# JavaEE
**Repository Path**: deng-chongshuang/java-ee
## Basic Information
- **Project Name**: JavaEE
- **Description**: JavaEE
- **Primary Language**: Java
- **License**: MIT
- **Default Branch**: Spring-Framework-6
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2024-08-16
- **Last Updated**: 2024-09-24
## Categories & Tags
**Categories**: Uncategorized
**Tags**: JavaEE, SpringFrameWork, Spring, DesignPatterns
## README
# Spring Framework 6
存放学习Spring6过程中的代码和笔记
学习的参考资料:
- [Spring视频零基础入门到高级,spring全套视频教程详解](https://www.bilibili.com/video/BV1Ft4y1g7Fb/?share_source=copy_web&vd_source=860f2b55b19d46a6df5c52b845497675)
- [老杜的笔记](https://www.yuque.com/dujubin/ltckqu/kipzgd?#) 密码:mg9b
## Spring的引入
### 一个简单的示例
[请查看代码](spring6-001-revelation/src)

上面这个简单的示例存在什么问题吗?
- 如果我想使用Oracle数据库我应该怎么办?
- Dao层添加一个Oracle的实现类,然后调用这个类所实现的方法
这样的话我们是不是应该对整个系统重新进行测试?
### 开闭原则
开闭原则(Open-Closed Principle, OCP)是软件设计的一个重要原则,它由伯纳德·查尔斯于1987年提出的。
开闭原则指出:
> 一个软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
这句话表明,软件的设计应该允许在不改变已有的代码的情况下添加新的功能或行为,而不是通过修改现有代码来实现。换句话说,一个好的设计应该是“开”得起来(open to extension),而不是“闭”得起来(closed to modification)。
为了达到这一目的,我们可以使用以下一些技巧:
1. **抽象化**: 抽象出公共的功能或接口,让它们成为独立的模块。
2. **继承和多态**: 使用继承和多态来定义一个类的行为,而不是硬编码。
3. **组合模式**: 将组件组合起来,使用不同的组合方式来实现不同功能。
4. **插件式设计**: 设计一个系统,以便可以通过插件或模块添加新的功能。
开闭原则是软件设计中非常重要的一个概念,它能使我们的代码更易于维护、扩展和重用。
### 依赖倒置
依赖倒置(Dependency Inversion Principle, DIP)是软件设计的一项原则,同样由伯纳德·查尔斯提出的。
依赖倒置的原则是:
> 高层次模块不应该依赖于低层次模块,而两个之间的依赖关系应该建立在接口或抽象上。
换句话说,高级别的模块(例如业务逻辑)不应该直接依赖于底层的实现细节。相反,它们应该通过一个抽象的接口来与低级别的模块通信。
依赖倒置原则有助于:
* 降低耦合性:高层次模块与低层次模块之间的依赖关系减少了。
* 提高可扩展性:可以更容易地添加新功能或切换到其他实现细节。
* 改善测试性:模块之间的依赖关系变得更加明确和简单。
依赖倒置原则有助于我们设计出更松耦合、易于维护和扩展的系统。
显然这个简单的示例违背了上诉两个原则,那么该如何解决呢?—— **控制反转**
### 控制反转
> 控制反转(Inversion of Control,IoC)是一种软件设计原则,它旨在将对象的创建和管理责任从应用程序代码中移出,并将其交给一个外部容器或框架来处理。通过控制反转,代码的耦合度降低,模块化和可测试性增强。
控制反转的基本概念
在传统的编程模式中,程序创建和管理其依赖性。这意味着对象需要主动地查找和管理其依赖的其他对象。控制反转将这种责任反转,由外部容器来负责对象的创建和管理。IoC 可以通过两种主要的实现方式来达到:依赖注入(Dependency Injection,DI)和服务定位器模式(Service Locator)。
#### 依赖注入(Dependency Injection)
依赖注入是控制反转的一种常见实现方式。它通过将对象的依赖关系作为参数传递给对象的构造函数或通过 setter 方法来实现,这样一来,对象就不再负责其依赖的创建和管理。
例子:
```java
// 依赖类
class Service {
public void serve() {
System.out.println("Service is serving");
}
}
// 使用依赖类的类
class Client {
private Service service;
// 通过构造函数注入依赖
public Client(Service service) {
this.service = service;
}
public void doSomething() {
service.serve();
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
Service service = new Service(); // 创建依赖
Client client = new Client(service); // 将依赖注入到 Client
client.doSomething();
}
}
```
在这个例子中,`Client` 类不再负责创建 `Service` 类的实例,而是通过构造函数接受了一个 `Service` 的实例。这就是控制反转的一个简单示例。
#### 服务定位器模式(Service Locator)
服务定位器是一种设计模式,在这种模式中,应用程序通过一个中央服务定位器来查找和获取所需的依赖对象。虽然这种方式也实现了控制反转,但它将对象的创建和查找集中到了服务定位器中,这可能会增加代码的复杂性。
```java
class ServiceLocator {
private static Service service;
public static Service getService() {
if (service == null) {
service = new Service(); // 创建服务实例
}
return service;
}
}
class Client {
public void doSomething() {
Service service = ServiceLocator.getService(); // 通过服务定位器获取服务
service.serve();
}
}
```
#### 控制反转的优点
1. **降低耦合**:通过将对象的创建和管理责任转移到外部容器,降低了各个组件之间的耦合度。
2. **增强可测试性**:使用依赖注入时,可以轻松替换依赖对象,进行单元测试和集成测试。
3. **提高可维护性**:当需要更改或扩展功能时,只需修改配置而不是修改代码。
4. **增强模块化**:使得不同模块之间的依赖关系更加清晰。
控制反转是一种重要的设计理念,广泛应用于现代软件开发,尤其是在使用框架(如 Spring、Guice 等)时。它帮助开发者构建松耦合、可测试和可维护的应用程序。
## Spring 概述
> Spring 是一个广泛使用的开源框架,主要用于简化 Java 企业级应用程序的开发。它提供了一整套基础设施支持,包括编程和配置的方式,使得开发者能够更容易地构建高效、可扩展和可维护的 Java 应用程序。Spring 框架的核心理念是通过控制反转(IoC)和面向切面编程(AOP)来实现松耦合和高内聚。
### Spring 的核心特性
1. **控制反转(IoC)**:
- Spring 框架通过 IoC 容器管理对象的生命周期和依赖关系。开发者只需定义对象及其依赖,Spring 容器会负责创建和注入这些依赖,从而实现松耦合。
2. **面向切面编程(AOP)**:
- Spring 支持 AOP,使得开发者可以将横切关注点(如日志、事务、安全等)与业务逻辑分开,增强代码的可维护性。
3. **数据访问**:
- Spring 提供了简化的 JDBC 访问、ORM 框架集成(如 Hibernate、JPA)以及事务管理,简化了与数据库的交互。
4. **模块化**:
- Spring 由多个模块组成,包括:
- **Spring Core**:IoC 和 AOP 的核心模块。
- **Spring MVC**:用于构建 Web 应用程序的模块。
- **Spring Boot**:简化 Spring 应用程序的开发过程,提供约定优于配置的方式。
- **Spring Security**:提供安全性功能的模块。
- **Spring Data**:简化数据访问和持久化的模块。
5. **测试支持**:
- Spring 提供了良好的单元测试支持,可以轻松地对 Spring 应用程序进行测试。
6. **集成**:
- Spring 提供与其他框架和技术的集成支持,例如 Hibernate、JPA、JMS、Quartz 等。
### Spring 的架构

Spring 的架构主要包含以下几个关键组件:
- **IoC 容器**:负责对象的实例化、配置和管理(如 Bean 的创建、生命周期管理等)。
- **AOP 框架**:提供对横切关注点的支持,允许开发者在不修改源代码的情况下添加功能。
- **Spring MVC**:提供实现 Web 应用程序的功能,支持 RESTful API 的创建。
- **Spring Boot**:用于快速构建和开发 Spring 应用程序,提供自动配置和开箱即用的功能。
### Spring Boot
Spring Boot 是一个用于简化 Spring 应用程序设置和开发的框架。它通过提供默认配置、嵌入式服务器(如 Tomcat、Jetty)以及自动化配置,极大地减少了开发者的配置工作,允许开发者专注于业务逻辑的实现。
### 总结
Spring 框架提供了一种灵活、强大且高效的方式来构建 Java 企业级应用程序。无论是 Web 应用程序、微服务还是简单的桌面应用程序,Spring 都能提供有效地解决方案。由于其强大的功能和广泛的社区支持,Spring 已成为现代 Java 开发的重要工具之一。
## 第一个 Spring 程序
### 通过 Maven 导入 Spring 的相关依赖
在Spring的GitHub仓库中我们可以找到Spring依赖导入的Maven配置 [Spring Framework Artifacts](https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Artifacts)
```xml
org.springframework
spring-context
6.1.4
```
### 将类的管理交给 Spring
我们可以在类路径(resources目录)下通过写XML配置文件的方式来将类交由Spring管理,具体可以查看 [`beans.xml`](spring6-002-first/src/main/resources/beans.xml)
### 获取 Bean 对象
`ApplicationContext` 可以用来获取Spring管理的Bean对象,具体可以查看 [`FirstTest.java`](spring6-002-first/src/test/java/cn/com/dcsgo/spring6/FirstTest.java)
### 小细节
- bean 标签的 id 属性能够重复吗?
- 不能,当id重复时IDEA会给出提示;但是Maven的validate与compile都不会报错,只有当运行到读取配置的代码时才会抛出异常 `BeanDefinitionParsingException: Configuration problem: Bean name 'userBean' is already used in this element`
- spring 底层是如何创建对象的?
- 通过反射机制,调用bean对象的无参构造方法来实例化bean对象;因此 **如果不是设计需要,那我们一定要提供无参构造方法!**
- spring 通过什么样的数据结构来管理 bean 对象呢?
- Map——键值对的形式来管理 bean 对象,其中键就是id,值就是对象的全限定名
- spring 对于配置文件的命名有要求吗?
- 没有,不过最好还是见名知意,不要用特殊字符
- spring 的配置文件可以有多个吗?
- 可以,`ClassPathXmlApplicationContext` 支持可变参数
- spring 只能管理自定义的类吗?
- 不是,完全可以管理其他类,例如:JDK中的、第三方类库中的
- getBean()方法调用时,如果指定的id不存在会怎样?
- 抛出:`NoSuchBeanDefinitionException` 异常
- getBean()方法返回的类型是Object,如果访问子类的特有属性和方法时,还需要向下转型,有其它办法可以解决这个问题吗?
- 使用这个方法 ` T getBean(String name, Class requiredType)` 传入bean对象的 `class` 对象即可
- ClassPathXmlApplicationContext是从类路径中加载配置文件,如果没有在类路径当中,又应该如何加载配置文件呢?
- 可以通过 `FileSystemXmlApplicationContext` 加载配置文件
### 集成Log4j2日志框架
- 导入依赖
```xml
org.apache.logging.log4j
log4j-core
2.19.0
org.apache.logging.log4j
log4j-slf4j2-impl
2.19.0
```
- 写配置文件,类路径下 `log4j2.xml` 中写入如下配置
```xml
```
- 使用日志
```java
//获取日志记录器 要记录日志的类的class对象
Logger logger= LoggerFactory.getLogger(FirstTest.class);
//记录日志
logger.info("我是一条日志信息");
logger.debug("我是一条调试信息");
logger.error("我是一条错误信息");
```
- [ ] 回顾 [`FirstTest.java`](spring6-002-first/src/test/java/cn/com/dcsgo/spring6/FirstTest.java)
## 依赖注入
### set 注入
- 需要注入依赖的类:提供set方法
```java
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
```
- spring配置:只需要在 `` 标签中添加 `` 标签,并设置 `name` 和 `ref` 属性,即可完成 set 注入的配置。
```xml
```
- `name`:set方法名省略掉set前缀后,将第一个单词首字母小写
- `ref`:要注入的bean的 `id`
### 构造注入
- 需要依赖注入的类:提供构造方法
```java
private final PermissionDao permissionDao;
private final RoleDao roleDao;
public RoleServiceImpl(PermissionDao permissionDao, RoleDao roleDao) {
logger.info("调用RoleServiceImpl的构造方法");
this.permissionDao = permissionDao;
this.roleDao = roleDao;
}
```
- spring配置:只需要在 `` 标签中添加 `` 标签,并设置 `ref` 属性,即可完成构造注入的配置。
```xml
```
- `ref`:要注入的bean的 `id`
- 除了 `ref` 外我们还可以设置
- `index`:构造方法中参数的下标,从0开始
- `name`:构造方法中参数的名称
> 有意思的是如果 `name` 属性设置错误即不为对应的参数名,spring并不会报错;而对于 `index`
> 属性,如果设置错误则会抛出 `UnsatisfiedDependencyException`,当然 `ref`
> 属性设置错误也会报错,抛出的是 `BeanCreationException`。
- [ ] [dao](spring6-003-dependency-injection/src/main/java/cn/com/dcsgo/dao)
- [ ] [service](spring6-003-dependency-injection/src/main/java/cn/com/dcsgo/service)
- [ ] [spring配置](spring6-003-dependency-injection/src/main/resources/beans.xml)
- [ ] [测试代码](spring6-003-dependency-injection/src/test/java/cn/com/cn)
### set 注入专题
#### 内部 bean 与外部 bean
```xml
```
> 通过 `ref` 引用的bean就是外部bean,在 `property` 标签中嵌套定义的bean就是内部bean
#### 注入简单值类型
对于一般的简单值类型我们只需要通过 `value` 属性即可完成注入,例如:
```xml
```
还有哪些是简单值类型呢?可以查看`org.springframework.util.ClassUtils` 中的 `isSimpleValueType(Class> type)` 方法:
```java
isSimpleValueType(Class> type) {
return !isVoidType(type) &&
(isPrimitiveOrWrapper(type)
|| Enum.class.isAssignableFrom(type)
|| CharSequence.class.isAssignableFrom(type)
|| Number.class.isAssignableFrom(type)
|| Date.class.isAssignableFrom(type)
|| Temporal.class.isAssignableFrom(type)
|| ZoneId.class.isAssignableFrom(type)
|| TimeZone.class.isAssignableFrom(type)
|| File.class.isAssignableFrom(type)
|| Path.class.isAssignableFrom(type)
|| Charset.class.isAssignableFrom(type)
|| Currency.class.isAssignableFrom(type)
|| InetAddress.class.isAssignableFrom(type)
|| URI.class == type || URL.class == type
|| UUID.class == type || Locale.class == type
|| Pattern.class == type || Class.class == type);
}
```
似乎可以用字符或字符串构造其对象实例的都被归为简单值类型
1. **基本数据类型和包装类**:
- **Primitive Types** (int, boolean 等) 和 **Wrapper Classes** (Integer, Boolean 等):
这些通常可以通过 `String` 使用相应的 `parse` 方法获取。例如,`Integer.parseInt("123")`
和 `Boolean.parseBoolean("true")`。
2. **Enum**:
- 枚举类可以通过 `Enum.valueOf()` 方法来使用字符串获取相应的实例。例如,`Color.valueOf("RED")`。
3. **CharSequence**:
- 任何实现了 `CharSequence` 接口的类型(如 `String`)都可以直接使用字符串进行实例化。
4. **Number**:
- 这是一个父类,具体的实现类(如 `Integer`, `Double`, `Long` 等)可以通过其 `parse` 方法从字符串创建实例。
5. **Date**:
- `java.util.Date` 可以通过 `SimpleDateFormat`
来使用字符串进行解析,例如 `new SimpleDateFormat("yyyy-MM-dd").parse("2023-10-10")`。
6. **Temporal**:
- `Temporal` 是一个接口,相关的实现类(如 `LocalDate`, `LocalDateTime`)可以通过 `DateTimeFormatter` 从字符串进行解析。
7. **ZoneId** 和 **TimeZone**:
- 可以通过 `ZoneId.of("America/New_York")` 和 `TimeZone.getTimeZone("GMT")` 从字符串获取实例。
8. **File**:
- 可以通过 `new File("path/to/file")` 从字符串创建文件对象。
9. **Path**:
- 可以使用 `Paths.get("path/to/file")` 从字符串创建 `Path` 对象。
10. **Charset**:
- 可以通过 `Charset.forName("UTF-8")` 从字符串获取字符集。
11. **Currency**:
- 可以通过 `Currency.getInstance("USD")` 从字符串获取货币实例。
12. **InetAddress**:
- 可以通过 `InetAddress.getByName("127.0.0.1")` 从字符串获取地址。
13. **URI** 和 **URL**:
- 可以通过 `new URI("http://example.com")` 和 `new URL("http://example.com")` 从字符串获取实例。
14. **UUID**:
- 可以通过 `UUID.fromString("550e8400-e29b-41d4-a716-446655440000")` 从字符串获取实例。
15. **Locale**:
- 可以通过 `new Locale("en", "US")` 进行创建,虽然这种方式需要多个字符串,但也可以从字符串表示获取。
16. **Pattern**:
- 可以通过 `Pattern.compile("regex")` 从字符串获取正则表达式模式。
17. **Class**:
- 通过 `Class.forName("com.example.MyClass")` 从字符串获取类对象。
需要注意的是 **Void**
> `Void` 是 Java 中的一个特殊类,它处于 `java.lang` 包中。虽然它的名字是 `Void`,但它并不代表一个真正的对象或值,而是表示 *
*没有返回值** 的概念。
1. **表示没有返回值**:
- 在 Java 中,`Void` 通常用于表示方法不返回任何值。例如,当你定义一个返回类型为 `Void` 的方法时,意味着这个方法不会返回任何值。
2. **不能实例化**:
- `Void` 类没有公共构造函数,因此无法直接创建 `Void` 的实例。它的用途主要是作为类型的占位符。
3. **常用场景**:
- `Void` 常用于泛型和反射中。例如,在使用 `Callable` 时,表示一个不返回结果的异步计算。
- 在一些框架中(如 Java 的 `CompletableFuture`),`Void` 作为返回类型使用,表示任务完成时不返回任何结果。
以下是一些使用 `Void` 的场景示例:
1. **方法返回类型为 `Void`**:
```java
public class Example {
public Void doSomething() {
System.out.println("Doing something...");
return null; // 返回 null
}
}
```
2. **使用 `Callable`**:
```java
import java.util.concurrent.Callable;
public class VoidExample {
public static void main(String[] args) {
Callable task = () -> {
System.out.println("Task executed.");
return null; // 返回 null
};
try {
task.call(); // 执行任务
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
`Void` 是一个特殊的类,主要用于表示没有返回值的概念,不能被实例化,也不包含任何实例。它在 Java 编程中常用于方法的返回类型、泛型和一些框架中的特定用途。
#### 级联属性赋值
> 级联属性(Cascading
> Properties)是一个用于描述对象之间关系的概念,特别是在面向对象编程和数据模型设计中。它指的是通过链式调用的方式访问一个对象内部的多个属性或方法。这种方式允许开发人员以更简洁和可读的方式操作复杂对象的属性。
例如,如果你有一个 `Person` 对象,它有一个 `Address` 对象,`Address` 对象又有一个 `City`
对象,那么可以通过 `person.getAddress().getCity().getName()` 来访问城市名称。
对于级联属性的赋值,在需要注入依赖的对象中添加get方法,当然注入的对象也要提供相应的set方法,以下是具体的例子:
```java
//依赖的类
public class Clazz {
private String name;
/**
* set 注入简单值类型 name
*
* @param name 班级名称
*/
public void setName(String name) {
this.name = name;
}
}
//需要注入依赖的类
public class Student {
private String name;
private Clazz clazz;
/**
* set 注入简单值类型 name
*
* @param name 姓名
*/
public void setName(String name) {
this.name = name;
}
/**
* set 注入依赖 clazz
*
* @param clazz 班级
*/
public void setClazz(Clazz clazz) {
this.clazz = clazz;
}
/**
* 级联属性赋值,提供属性的get方法
* @return clazz
*/
public Clazz getClazz() {
return this.clazz;
}
}
```
XML配置:
```xml
```
需要注意的是:`` 与 `` 的 **顺序不能交换
** ,否则会抛出 `BeanCreationException`
```java
BeanCreationException: Error creating bean with name 'student' defined in class path resource [cascade.xml]: nvalid property 'clazz' of bean class [cn.com.dcsgo.entity.Student]: Value of nested property 'clazz' is null
```
#### 注入数组
注入数组时使用 `` 标签,数组元素为简单值类型时使用 `` 标签,数组元素为对象时使用 `` 标签或 `[` 标签。
需要注入依赖的类
```java
public class Faculty {
private String name;
private Clazz[] classes;
private String[] instructors;
public void setName(String name) {
this.name = name;
}
public void setClasses(Clazz[] classes) {
this.classes = classes;
}
public void setInstructors(String[] instructors) {
this.instructors = instructors;
}
}
```
XML配置:
```xml
]
张导
王导
刘导
```
#### 注入集合
- 注入List集合使用 `` 标签
- 元素类型 **为简单值类型** 使用 `` 标签
- 元素类型 **不为简单值类型** 使用 `` 标签或 `[` 标签
- 注入Set集合使用 `` 标签
- 元素类型 **为简单值类型** 使用 `` 标签
- 元素类型 **不为简单值类型** 使用 `` 标签或 `][` 标签
- 注入Map集合使用 `]