# 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) ![简单的实例](images/001-simpleDemo.png) 上面这个简单的示例存在什么问题吗? - 如果我想使用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 8 大模块](images/001-springStructure.png) 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集合使用 `` 标签 - 每一个元素用 `` 标签表示 - **键值均为简单值类型** 使用 `` 标签的 `key` 和 `value` 属性 - **键为简单值类型,值不为简单值类型** 使用 `` 标签的 `key` 属性和 `value-ref`属性 或 `` 标签 - **键不为简单值类型,值是简单值类型** 使用 `` 标签的 `value` 属性和 `key-ref` 属性 或 `` 标签 - **键值均不为简单值类型** 使用 `` 标签的 `key-ref` 属性和 `value-ref` 属性 或 `` 和 `` 标签 - 注入Properties集合使用 `` 标签 - 每一个配置项用 `` 标签表示,键使用 `key` 属性 - [ ] [查看依赖类与被依赖类](spring6-003-dependency-injection/src/main/java/cn/com/dcsgo/entity) - [ ] [查看 `collection.xml`](spring6-003-dependency-injection/src/main/resources/collection.xml) - [ ] [查看 `ArrayTest.java`](spring6-003-dependency-injection/src/test/java/cn/com/cn/ArrayTest.java) - [ ] [查看 `CollectionTest.java`](spring6-003-dependency-injection/src/test/java/cn/com/cn/CollectionTest.java) #### 注入null与空字符串 - 注入null使用 `` 标签或不设置要注入null的属性 - 注入空字符串使用 `` 标签或 `` 标签或将value属性的值设置为 `""` ```xml ``` #### 注入的值中含有的特殊字符 > 这里的特殊字符指的是XML中规定的几个特殊字符,它们是: `<`、`>`、`'`、`"`、`&` > ,以上5个字符会被当做XML语法的一部分进行解析,所以不能直接在XML中书写,需要使用XML的转义字符或使用CDATA区段来书写。 不过实测发现 `>` 和 `'` 是可以正常使用的,不过为了避免出现意料之外的bug,还是建议用转义字符或CDATA。 - 使用转义字符 - `<` 使用 `<` 标识 - `>` 使用 `>` 标识 - `'` 使用 `'` 标识 - `"` 使用 `"` 标识 - `&` 使用 `&` 标识 - 使用CDATA区段 `` ```xml ]]> ``` 效果 ``` calzz1: Clazz{name='< "高三一班" & '高三二班' >'} calzz2: Clazz{name='< "高三一班" & '高三二班' >'} ``` 注意:**使用CDATA时,不能使用value属性,只能使用value标签**。 ### 基于命名空间注入依赖 #### p 命名空间注入 > p命名空间是简化set注入的,这要求我们必须 **提供相应的set方法**。 - 使用 `xmlns` 属性引入命名空间 ```xml xmlns:p="http://www.springframework.org/schema/p" ``` - 使用 `p` 命名空间注入依赖 ```xml ``` - [ ] [配置文件 `p.xml`](spring6-003-dependency-injection/src/main/resources/p.xml) - [ ] [测试代码 `PTest.java`](spring6-003-dependency-injection/src/test/java/cn/com/cn/PTest.java) #### c 命名空间注入 > c命名空间是简化构造注入的,这要求我们必须 **提供相应的构造方法**。 - 使用 `xmlns` 属性引入命名空间 ```xml xmlns:c="http://www.springframework.org/schema/c" ``` - 使用 `c` 命名空间注入依赖 ```xml ``` - [ ] [配置文件 `c.xml`](spring6-003-dependency-injection/src/main/resources/c.xml) - [ ] [测试代码 `CTest.java`](spring6-003-dependency-injection/src/test/java/cn/com/cn/CTest.java) #### util 命名空间注入 > util命名空间用于提供集合和其他实用工具类的配置。使用util命名空间可以方便地定义和注入Java集合(如List、Set、Map)和其他类。 - 使用 `xmlns` 属性引入命名空间 ```xml xmlns:util="http://www.springframework.org/schema/util" ``` - 使用 `xsi:schemaLocation` 指定util命名空间对应的Schema文件的URL ```xml http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd ``` - 使用 `util` 命名空间注入依赖 ```xml coder coder@dcsgo.com.cn ``` - [ ] [配置文件 `c.util`](spring6-003-dependency-injection/src/main/resources/util.xml) - [ ] [注入依赖的类](spring6-003-dependency-injection/src/main/java/cn/com/dcsgo/config) - [ ] [测试代码 `UtilTest.java`](spring6-003-dependency-injection/src/test/java/cn/com/cn/UtilTest.java) #### 配置文件中的`` 标签 ```xml ``` > **``**: 这是 Spring 配置文件的根元素,它用于定义 Spring 应用上下文中的 Bean。所有的 Bean 定义都应该在这个元素内。 ##### 1. `xmlns` 属性 - **`xmlns`**: 默认命名空间。这里声明了默认命名空间为 `http://www.springframework.org/schema/beans`,这意味着在该 XML 文件中,所有未明确指定命名空间的元素将被视为这个命名空间下的元素。例如, `` 标签会被视为 `http://www.springframework.org/schema/beans` 命名空间中的 Bean 定义。 - **`xmlns:xsi`**: 这是 XML Schema 实例命名空间,通常用于 Schema 验证。其声明语法为 `xmlns: xsi="http://www.w3.org/2001/XMLSchema-instance"`,表示该 XML 文档使用的 Schema 实例属性。 - **`xmlns:util`**: 这是一个自定义命名空间,指向 `http://www.springframework.org/schema/util`,用于定义 Spring 提供的集合和实用工具类,比如 List、Map、Set 等。它允许使用 ``、`` 等标签来简化集合的定义。 ##### 2. `xsi:schemaLocation` 属性 - **`xsi:schemaLocation`**: 该属性用于指定 XML 文档使用的 Schema 位置,格式为 `namespace URI schemaLocation` 。它的作用是告诉解析器在哪里可以找到与命名空间相关的 XML Schema 文件,以便进行结构验证。 ##### 作用和重要性 1. **结构验证**: 使用 `xsi:schemaLocation`,XML 解析器可以验证整个 XML 文件是否符合所定义的 Schema 规则,确保文件的结构和内容的正确性。 2. **清晰的命名空间管理**: 通过命名空间声明,可以在同一个 XML 文件中使用多个不同的 Schema,而不会产生混淆。这在使用 Spring 的不同模块(如 Beans、Util、Context 等)时特别有用。 3. **提高可读性和可维护性**: 明确的命名空间和结构可以帮助开发者更容易理解和维护 XML 配置文件。IDE 如果配置得当,还可以提供更好的自动补全和语法检查功能。 ### 基于XML的自动装配 #### 根据名称自动装配 > 根据set方法名进行自动装配。 - 提供无参构造方法 - 提供set方法 - set方法名要与注入类的id相匹配 - `` 标签中添加 `autowire="byName"` 属性 ```xml ``` #### 根据类型自动装配 > 根据set方法中的参数类型进行自动装配。 - 提供无参构造方法 - 提供set方法 - `` 标签中添加 `autowire="byType"` 属性 - 有效的配置文件中与set方法的参数类型相同的bean不能有多个,即只能有一个 - 当有同级的继承或实现时,spring会选择代码中第一个set方法进行注入 这里对最后一点进行进一步的举例说明: XML配置如下: ```xml ``` 类图如下: ![classDiagram](images/003-classDiagram.png) set方法如下: ```java public void setPermissionDao(PermissionDaoImpl permissionDao) { logger.info("调用 setPermissionDao(PermissionDaoImpl) 方法"); this.permissionDao = permissionDao; } public void setPermissionDao(PermissionDaoImplPlus permissionDao) { logger.info("调用 setPermissionDao(PermissionDaoImplPlus) 方法"); this.permissionDao = permissionDao; } ``` 只需要调换上面两个set方法的顺序,程序就会有不同的执行结果 - [ ] [`PermissionServiceImpl.java`](spring6-003-dependency-injection/src/main/java/cn/com/dcsgo/service/impl/PermissionServiceImpl.java) - [ ] [`AutowiredByXMLTest.java`](spring6-003-dependency-injection/src/test/java/cn/com/cn/AutowiredByXMLTest.java) ### 引入外部配置文件 > Spring还可以引用外部配置文件,并将配置文件中的配置注入到Bean中。 下面以引用 `.properties` 文件为例: - 使用 `xmlns` 属性引入命名空间 ```xml xmlns:context="http://www.springframework.org/schema/context" ``` - 使用 `xsi:schemaLocation` 指定context命名空间对应的Schema文件的URL ```xml http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd ``` - 使用 `context:property-placeholder` 引入外部配置文件 ```xml ``` - 使用 `${配置文件中的属性名}` 引用外部配置文件中的属性 ```xml ``` - [ ] [`jdbc.properties`](spring6-003-dependency-injection/src/main/resources/jdbc.properties) - [ ] [`properties.xml`](spring6-003-dependency-injection/src/main/resources/properties.xml) - [ ] [`PropertiesTest.java`](spring6-003-dependency-injection/src/test/java/cn/com/cn/PropertiesTest.java) 需要注意的是:在引用外部文件配置时,**环境变量会覆盖配置文件中的一些配置**;例如上面的 `username` ,如果配置文件中也是使用的 `username` 的话,实际注入的值就可能是Administrator或其他值,这取决于环境变量的设置;因此我们可以修改配置文件中的属性名,以避免这种情况发生。 ## Bean的作用域 > Spring中Bean有两个基本的作用域:单例(Singleton)和原型(Prototype),我们也可以自定义Bean的作用域;通过引入其他依赖,Bean的作用域也会发生一定的改变。 配置Bean的作用域只需要在 `` 标签中设置 `scope` 属性即可,以下是一些常用的作用域(引用自[老杜的笔记](https://www.yuque.com/dujubin/ltckqu/kipzgd?#) 密码:mg9b): - singleton:单例,默认的。在加载配置时完成类的实例化。 - prototype:原型。每调用一次getBean()方法则获取一个新的Bean对象,或每次注入的时候都是新对象,在获取或注入时实例化对象 - request:一个请求对应一个Bean,仅限于在WEB应用中使用。 - session:一个会话对应一个Bean,仅限于在WEB应用中使用。 - global session:portlet应用中专用的,如果在Servlet的WEB应用中使用global session的话,和session一个效果。(portlet和servlet都是规范。servlet运行在servlet容器中,例如Tomcat。portlet运行在portlet容器中。) - application:一个应用对应一个Bean。仅限于在WEB应用中使用。 - websocket:一个websocket生命周期对应一个Bean。仅限于在WEB应用中使用。 - 自定义scope - [ ] [`basic.xml`](spring6-004-scope/src/main/resources/basic.xml) - [ ] [`BasicScopeTest.java`](spring6-004-scope/src/test/java/cn/com/dcsgo/BasicScopeTest.java) ### 自定义作用域 下面以自定义一个线程范围内的作用域与为例: 1. 自定义类实现 `Scope` 接口 - Spring已经实现了一个线程范围的类: `SimpleThreadScope` ,我们可以直接使用。 2. 将自定义的Scope注册到Spring的IoC容器中 ```xml ``` 3. 在 `` 标签中设置 `scope` 属性为自定义的scope的名称 ```xml ``` - [ ] [`CustomScopeTest.java`](spring6-004-scope/src/test/java/cn/com/dcsgo/CustomScopeTest.java) ## 工厂模式 这里直接引用[老杜的笔记](https://www.yuque.com/dujubin/ltckqu/kipzgd?#jF9z2)(密码:mg9b)中的内容 ### 简单工厂 示例类图: ![factoryPattern-simpleFactoryClassDiagram](images/factoryPattern-simpleFactoryClassDiagram.png) - [ ] [简单工厂实例代码](factory-pattern/src/main/java/cn/com/dcsgo/factory/pattern/simple/factory) ### 工厂方法模式 示例类图: ![factoryPattern-factoryMethodClassDiagram](images/factoryPattern-factoryMethodClassDiagram.png) - [ ] [工厂方法模式示例代码](factory-pattern/src/main/java/cn/com/dcsgo/factory/pattern/factory/method) ### 设计模式 - [ ] [设计模式学习](https://gitee.com/deng-chongshuang/java-ee/tree/Design-Patterns/) ## Bean 的实例化方式 ### 使用 Spring 默认方式实例化 在前面依赖注入章节中,构造注入和set注入就是使用Spring默认方式实例化Bean对象的,这种方式不需要特殊配置,Spring会自动完成Bean的实例化。 ### 通过简单工厂实例化 - [ ] [回顾工厂模式](#工厂模式) > 通过简单工厂实例化Bean对象,需要配置 `` 标签的 `factory-method` 属性,该属性指定了实例化Bean对象的方法;`class` > 属性需要修改为工厂类的全限定名。 具体产品角色: ```java public class SampleBean { public SampleBean() { System.out.println("SampleBean无参构造方法执行..."); } } ``` 简单工厂角色: ```java public class SimpleFactory { public static SampleBean getSampleBean() { return new SampleBean(); } } ``` XML配置: ```xml ``` ### 通过工厂方法模式实例化 > 通过工厂方法实例化Bean对象,需要配置 `` 标签的 `factory-bean` > 属性,该属性指定了工厂Bean的名称,以及 `factory-method` 属性,该属性指定了实例化Bean对象的方法。 具体产品角色: ```java public class SampleBean { public SampleBean() { System.out.println("SampleBean无参构造方法执行..."); } } ``` 具体工厂角色: ```java public class SampleBeanFactory { public SampleBean getSampleBean() { return new SampleBean(); } } ``` XML配置: ```xml ``` ### 通过FactoryBean实例化 > 具体工厂角色实现 `FactoryBean` 接口,并重写 `getObject()` 方法,该方法返回一个Bean对象;重写 `isSingleton` > 可对Bean对象的 `scope` 是否为 `singleton` 进行控制。 具体产品角色: ```java public class SampleBean { public SampleBean() { System.out.println("SampleBean无参构造方法执行..."); } } ``` 具体工厂角色: ```java public class SampleBeanFactoryImplFactoryBean implements FactoryBean { @Override public SampleBean getObject() throws Exception { return new SampleBean(); } @Override public Class getObjectType() { return null; } @Override public boolean isSingleton() { return true; } } ``` XML配置: ```xml ``` #### 注入 LocalDate 示例 > 日期时间对象在Spring中属于简单值类型,可以通过 `value` > 属性直接注入,但是对格式有要求;如果需要自定义格式我们就可以通过实现 `FactoryBean` 来完成这一业务。 需要注入依赖的对象: ```java public class Student { private LocalDate birthday; public Student() { } public void setBirthday(LocalDate birthday) { this.birthday = birthday; } @Override public String toString() { return "Student{" + "birthday=" + birthday + '}'; } } ``` 日期工厂: ```java public class LocalDateFactory implements FactoryBean { private final String date; private final DateTimeFormatter dateTimeFormatter; // 定义支持的分隔符 private final String[] separators = {"-", "/", "\\", " ", "."}; public LocalDateFactory(String date) { this.date = date; this.dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-M-d"); } @Override public LocalDate getObject() throws Exception { // 查找第一个分隔符 String separator = null; for (String s : separators) { if (date.contains(s)) { separator = s; break; } } // 如果找到了分隔符,则进行替换并解析 if (separator != null) { String adjustedDate = date.replace(separator, "-"); return LocalDate.parse(adjustedDate, dateTimeFormatter); } else { throw new IllegalArgumentException("Date separators in " + date + " are not currently supported"); } } @Override public Class getObjectType() { return null; } @Override public boolean isSingleton() { return FactoryBean.super.isSingleton(); } } ``` XML配置: ```xml ``` - [ ] [测试代码](spring6-005-bean-instantiation/src/test/java/cn/com/dcsgo/bean) ### BeanFactory 与 FactoryBean 的区别 - `BeanFactory` 是 Spring 容器中的顶层接口,它定义了Spring容器的基本功能,包括Bean的创建、配置和管理等。 - `FactoryBean` 是 Spring 容器中的一种特殊Bean,它允许开发者自定义Bean的创建过程。通过实现 `FactoryBean` 接口,开发者可以控制Bean的创建过程,包括Bean的类型、作用域等。 ## Bean 的生命周期 > Bean 的生命周期指的是一个 Bean 从被创建到被销毁的整个过程。Spring 提供了一个完整的生命周期管理机制,可以自动处理 Bean 的创建、初始化、使用、销毁等过程。了解 Bean 的生命周期有助于开发者更好地管理和使用 Spring 中的 Bean。 ### Bean 的5个生命周期 ```mermaid graph LR 实例化[实例化Bean] -->|依赖注入| 属性赋值[设置属性] 属性赋值 -->|初始化| 初始化[执行init方法] 初始化 -->|使用| 使用[Bean被使用] 使用 -->|销毁| 销毁[执行destroy方法] ``` 1. **实例化**: - 当 Spring 容器启动时,首先会根据配置(XML 或注解)实例化 Bean。此时,Spring 会调用类的构造方法 2. **设置属性**: - 在实例化后,Spring 会根据配置文件或注解,将相关的属性(依赖)注入到 Bean 中。这一过程被称为依赖注入 3. **调用自定义的初始化方法(可选)**: - 如果在配置中指定了自定义的初始化方法,Spring 会在上述步骤完成后调用该方法 - 配置方法:指定 `` 标签的 `init-method` 属性为自定义初始化方法的名称即可 4. **Bean 的使用**: - 此时,Bean 已经完成初始化,可以被应用程序使用 5. **调用自定义的销毁方法(可选)**: - 如果在配置中指定了自定义的销毁方法,Spring 会在 Bean 被销毁之前调用该方法 - 配置方法:指定 `` 标签的 `destroy-method` 属性为自定义销毁方法的名称即可 销毁方法的调用通常是在 **IoC容器关闭时** 才会发生,例如调用 `ClassPathXmlApplicationContext` 的 `close()` 方法。 - [ ] [bean对象](spring6-006-bean-lifecycle/src/main/java/cn/com/dcsgo/bean) - [ ] [`5Steps.xml`](spring6-006-bean-lifecycle/src/main/resources/5Steps.xml) - [ ] [`StepsOf5Test.java`](spring6-006-bean-lifecycle/src/test/java/cn/com/dcsgo/StepsOf5Test.java)