Spring相关概念
Spring组成
Spring框架主要的优势是在 简化开发 和 框架整合 上,至于如何实现就是我们要学习Spring框架的主要内容:
简化开发: Spring框架中提供了两个大的核心技术,分别是:
- IOC
- AOP
- 事务处理(这属于AOP的具体应用,可以简化项目中的事务管理)
框架整合: Spring在框架整合这块已经做到了极致,它可以整合市面上几乎所有主流框架,比如:MyBatis、MyBatis-plus、Struts、Struts2、Hibernate……
- 在Spring框架的学习中,我们主要是学习如何整合MyBatis。
综上所述,对于Spring的学习,主要学习四块内容:
- IOC
- 整合Mybatis(IOC的具体应用)
- AOP
- 声明式事务(AOP的具体应用)
Spring生态圈
官网:spring.io, 从官网可以了解到:
- Spring能做什么:用以开发web、微服务以及分布式系统等,光这三块就已经占了JavaEE开发的九成多。
- Spring并不是单一的一个技术,而是一个大家族,可以从官网的
Projects中查看其包含的所有技术。
Spring发展到今天已经形成了一种开发的生态圈,Spring提供了若干个项目,每个项目用于完成特定的功能。
- Spring已形成了完整的生态圈,也就是说我们可以完全使用Spring技术完成整个项目的构建、设计与开发。
- Spring有若干个项目,可以根据需要自行选择,把这些个项目组合起来,起了一个名称叫Spring全家桶,如下图所示:
说明:
图中的图标都代表什么含义,可以进入 spring.io/projects 网站进行对比查看。
这些技术并不是所有的都需要学习,额外需要重点关注Spring Framework、SpringBoot和SpringCloud:
- Spring Framework:Spring框架,是Spring中最早最核心的技术,也是所有其他技术的基础。
- SpringBoot:Spring是来简化开发,而SpringBoot是来帮助Spring在简化的基础上能更快速进行开发。
- SpringCloud:这个是用来做分布式之微服务架构的相关开发。
除了上面的这三个技术外,还有很多其他的技术,也比较流行,如SpringData,SpringSecurity等,这些都可以被应用在我们的项目中。我们这里所学习的Spring其实指的是Spring Framework。
- 随着时间推移,版本不断更新维护,目前最新的是Spring5
- Spring其实是Spring家族中的Spring Framework
- Spring Framework是Spring家族中其他框架的底层基础,学好Spring可以为其他Spring框架的学习打好基础
Spring系统架构
前面我们说spring指的是Spring Framework,那么它其中都包含哪些内容以及我们该如何学习这个框架?
针对这些问题,我们将从系统架构图和课程学习路线来进行说明:
系统架构图
Spring Framework的5版本目前没有最新的架构图,而最新的是4版本,Spring 4 的架构图清晰地展示了其分层架构”的理念,每一层都构建在下一层之上,并提供了不同的服务,开发者可以根据需要选择使用其中任意一层。
- 核心层: Core Container:核心容器,这个模块是Spring最核心的模块,其他的都需要依赖该模块
- AOP层:
- AOP:面向切面编程,它依赖核心层容器,目的是在不改变原有代码的前提下对其进行功能增强
- Aspects:AOP是思想,Aspects是对AOP思想的具体实现
- 数据层:
- Data Access:数据访问,Spring全家桶中有对数据访问的具体实现技术
- Data Integration:数据集成,Spring支持整合其他的数据层解决方案,比如Mybatis
- Transactions:事务,Spring中事务管理是Spring AOP的一个具体实现,也是后期学习的重点内容
- Web层:这一层的内容将在SpringMVC框架具体学习
- Test层:该模块主要整合了JUnit(或TestNG)测试框架来完成单元测试和集成测试
Spring核心概念
在Spring核心概念这部分内容中主要包含IOC/DI、IOC容器和Bean,那么问题就来了,这些都是什么呢?
目前项目中的问题
数据层实现:
// BookDaoImpl1.java
public class BookDaoImpl1 implements BookDao {
public void save() {
System.out.println("book dao save ... 1");
}
}
// BookDaoImpl2.java
public class BookDaoImpl2 implements BookDao {
public void save() {
System.out.println("book dao save ... 2");
}
}
业务层实现:
public class BookServlet extends BookServlet {
//这个地方是写死了的,耦合度高
private BookDao bookDao = new BookDaoImpl();
public void save() {
bookDao.save();
}
}
在当前的代码结构中,主要存在以下问题:
- 高耦合度:业务层(如
BookServlet)需要直接new出数据层(如BookDaoImpl)的具体实现对象。 - 难以维护:当数据层的实现类发生变更时(例如,从
BookDaoImpl1切换到BookDaoImpl2),业务层的代码也必须随之修改。这会导致每次变更都需要重新编译、打包和部署。 - 缺乏灵活性:代码严重依赖于具体的实现类,而不是接口,使得程序难以扩展和测试。
所以现在的问题就是,业务层不想new对象,运行的时候又需要这个对象,该怎么办呢?
解决方案:Spring的核心思想
为了解决上述问题,Spring提出了IOC(控制反转) 和 DI(依赖注入) 的核心概念。
IOC、IOC容器、Bean、DI
- IOC (Inversion of Control)控制反转
- 什么是控制反转?
使用对象时,由主动在代码内部new产生对象,转变为由外部来提供对象。对象创建的控制权由程序代码转移到了外部容器(Spring IOC容器),这种思想称为控制反转(IOC)。 - Spring与IOC的关系
Spring技术对IOC思想进行了具体的实现,提供了一个容器,称为 IOC容器,它就是IOC思想中的那个“外部”。 - IOC容器的作用
IOC容器 负责对象的创建、初始化等一系列工作,是一个存放着众多Bean的大工厂。
这些被IOC容器创建和管理的对象统称为 Bean。
- DI (Dependency Injection)依赖注入
- 什么是依赖注入?
在IOC容器中,建立Bean与Bean之间依赖关系的整个过程,就称为依赖注入。(解决了问题:虽然IOC容器帮我们创建了所有对象,但对象与对象之间的依赖关系还未建立) - 如何工作?
程序员只需通过配置(XML或注解)告诉IOC容器,哪些Bean之间需要建立依赖关系(例如业务层需要依赖数据层,Service就需要依赖Dao)。容器在初始化时,就会自动将被依赖的对象赋值给成员变量,从而完成绑定。 - 最终目标是:充分解耦。
通过IOC容器管理所有Bean(IOC),并在容器内部将有依赖关系的Bean进行绑定(DI),使得我们在使用对象时,不仅能直接从容器中获取,而且获取到的Bean已经自动处理好了所有的依赖关系,程序员无需再关心复杂的对象创建和依赖组装过程。
IOC和DI入门案例
案例演示 Sprin实现IOC和DI的具体过程:
代码实现
逻辑步骤
-
创建一个标准、全新的 Java Maven 项目
-
添加 Spring 依赖 (IOC 基础):
在 pom.xml 中添加 Spring 上下文依赖(spring-context)和 JUnit 测试依赖(用于后续测试)<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> -
创建数据访问层(DAO)接口及其实现类:
public interface BookDao { public void save(); }public class BookDaoImpl implements BookDao { public void save() { System.out.println("book dao save ..."); } } -
创建业务服务层(Service)接口:
// BookService.java public interface BookService { public void save(); } -
创建业务服务层(Service)接口的实现类 (IoC服务类):
将依赖对象的创建和管理权从类内部转移到外部IOC容器-
声明一个 需要的外部依赖(Dao层)接口 类型的属性,用于依赖注入
-
提供该属性的 setter 方法,这是 DI 依赖注入 的通道。
-
在Bean初始化阶段,容器自动调用此方法注入依赖;方法参数 是DAO层接口的实现类实例,由IOC容器提供。
-
setter方法必须严格按照JavaBean的规范命名(即
set+首字母大写的属性名),Spring才能正确找到并调用它。
- 使用注入的外部依赖对象(DAO层) 调用 DAO 层的方法,以执行业务操作
// BookServiceImpl.java public class BookServiceImpl implements BookService { //1. 依赖声明,使用接口类型而非具体实现类 private BookDao bookDao; //2. Setter方法,依赖注入的通道 public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } //3. 使用注入的依赖调用业务方法,执行业务操作 public void save() { System.out.println("book service save ..."); bookDao.save(); // 使用注入的依赖 bookDao 调用 DAO 层的方法 } } -
-
创建 Spring 配置文件:将对象创建权交给 Spring (IOC 配置);由Spring容器建立Bean与Bean之间的依赖关系 (DI 配置)
-
在
src/main/resources目录下,右键 -> 新建 -> XML配置文件 -> Spring配置 ,创建 applicationContext.xml: -
添加 Spring beans 的命名空间和架构定义
-
配置 DAO 层和Service层的 bean(使用
<bean>标签):
- IOC 配置 ,
<bean>标签表示配置bean,以将 类的对象创建权 交给Spring容器:id属性表示给bean命名;class属性表示该bean的类型,指定将哪个类的对象创建权 交给Spring容器
- DI 配置,使用
<property>标签配置 Service层的 bean,以引用容器中其他bean:name属性表示属性名称(与setter方法名对应,遵循JavaBean规范);ref属性是该 bean引用容器中其他bean的id。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 配置数据访问层bean --> <bean id="bookDao" class="com.example.dao.impl.BookDaoImpl"/> <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/> <!-- 配置业务层bean --> <bean id="bookService" class="com.example.service.impl.BookServiceImpl"> <!-- 配置service与dao的依赖关系 --> <property name="bookDao" ref="bookDao"/> </bean> </beans> -
- 注意:bean定义时,id属性在同一个上下文中(配置文件)不能重复
-
创建应用程序主类,以从容器获取 Bean 并使用方法 (IOC使用方式):
- 在 main 方法中获取 Spring IOC 容器
- 使用
getBean()方法从容器获取 Service Bean对象
IOC 容器获取 Bean对象 的标准句式:
[接口类型] [变量名] = ([接口类型]) [容器变量名].getBean("[Bean名称]");
[接口类型]: 要获取的 Bean 实现的接口类型[变量名]: 接收 Bean 实例的变量名(通常使用接口名的小驼峰形式)[容器变量名]: ApplicationContext 实例的变量名(通常为context)[Bean名称]: 在 Spring 配置中定义的 Bean ID/名称
- 调用 Service 层的方法
public class App { public static void main(String[] args) { // 1. 获取IOC容器 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 2. 获取bean对象 BookService bookService = (BookService) context.getBean("bookService"); // 3. 调用方法 bookService.save(); } } -
运行结果
运行程序后,控制台将输出:book service save ... book dao save ...
注入标签详解
<property>注入标签:用于setter注入
<constructor-arg>注入标签:用于构造器参数注入
主要属性
- name 属性:指定当前正在配置的Bean对应的类中的属性名,表示将ref指定id的Bean对象依赖注入到其中。
- Spring 会根据名称将首字母大写,前面加"set"来寻找对应的setter方法
- name="bookDao" :表示要配置当前bean的哪个属性(对应setter方法名)
- 例如:
name="bookDao"→ 寻找setBookDao()方法
- ref 属性:指定要引用的另一个bean的id,Spring会在IOC容器中查找对应id的bean进行注入
- ref="bookDao" :表示要引用IOC容器中哪个bean(对应bean的id)
- 例如:
ref="bookDao"→ 引用id为"bookDao"的bean
- 可选属性:除了使用
name属性,在可能有多个参数的构造方法注入中,还可以使用index或type属性来指定参数:index:参数的索引位置(可选,从0开始)type:参数的类型(可选,用于消除歧义)
<!-- 使用索引方式 --> <constructor-arg index="0" ref="bookDao"/> <!-- 使用类型方式 --> <constructor-arg type="com.itheima.dao.BookDao" ref="bookDao"/>
可选属性:除了使用 name 属性,在配置构造器注入 (<constructor-arg>) 时,还可以使用 index 或 type 属性来精确指定参数,这对于解决歧义至关重要:
index:参数的索引位置(可选,从0开始)。
主要用于:当构造方法有多个相同类型的参数时,显式指定参数的索引位置,避免因配置顺序错误导致的注入问题。type:参数的类型(可选,值为 全限定类名或基本类型关键字)。
主要用于:①帮助 Spring 在多个重载的参数数量相同且类型不同的构造方法中,通过指定类型匹配对应的一个构造方法;②确保简单类型(如int,long,double)的值被准确转换,消除类型歧义。
注意:Setter 注入 (
<property>) 中 ,index属性极少使用,因为每个属性独立配置。但type属性在注入简单数据类型时依然推荐使用,以明确指定目标类型。
实践建议
- Setter 注入:优先使用
name属性,清晰易懂 - 构造器注入:
- 参数少时使用
name属性 - 参数多或相同类型时使用
index属性 - 需要消除类型歧义时使用
type属性
- 参数少时使用
- 简单类型注入:使用
type属性明确指定数据类型,避免自动类型转换的问题
注入多个依赖
- 可以在一个
<bean>内使用多个<property>(或<constructor-arg>)标签来注入多个依赖 <property>标签的顺序:顺序不影响注入结果,因为 Spring 会解析所有依赖后再进行注入<constructor-arg>标签的顺序:如果未使用name或index属性,那么<constructor-arg>标签的顺序必须与构造方法中参数的声明顺序严格一致,因为 Spring 默认按索引匹配。
必须提供 setter(或 构造)方法:
每个需要注入的属性都必须在类中有对应的 setter(或 构造)方法构造器紧耦合问题:
构造器注入使用name属性时,当构造函数中方法的参数名发生变化后,配置文件中的name属性也需要跟着变。
解决:使用index、type属性替换name属性能一定程度上解决耦合问题。
局限:
- 使用
type属性时,如果构造方法参数中有类型相同的参数,这种方式就不能使用了- 使用
index属性时,如果构造方法参数顺序发生变化后,这种方式又带来了新的耦合问题。
IOC相关内容
通过前面案例,学习了bean如何定义配置,DI如何定义配置以及容器对象如何获取的内容,接下来把这三块内容展开进行详细的讲解,深入的学习下这三部分的内容。
Bean 基础配置
Bean 基本配置(id 与 class)
在 Spring 配置中,bean 的基本配置使用以下格式:
<bean id="唯一标识符" class="类的全限定名"/>
- bean 标签:用于配置由 Spring 容器创建和管理的对象
- id 属性:给 bean 起一个同一配置文件中唯一的标识名称,用于在容器中查找该 bean
- class 属性:指定 bean 的全限定类名(必须是具体实现类,不能是接口)
Bean 的 name 属性(别名配置)
除了使用 id 属性,还可以使用 name 属性为 bean 配置一个或多个别名:
<bean id="bookService" name="service service4 bookEbi" class="com.example.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
<bean id="bookDao" name="dao" class="com.example.dao.impl.BookDaoImpl"/>
- name 属性:为 bean 指定一个或多个别名,多个别名可以使用逗号、分号或空格进行分隔
- EBI:全称 Enterprise Business Interface,是企业业务接口的缩写
获取 Bean 对象的方式
可以通过 id 或 name 属性中的任意一个值来获取 bean 对象:
public class AppForName {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 以下三种方式都可以获取到同一个bean对象
BookService service1 = (BookService) ctx.getBean("bookService"); // 使用id
BookService service2 = (BookService) ctx.getBean("service"); // 使用name中的第一个别名
BookService service3 = (BookService) ctx.getBean("service4"); // 使用name中的第二个别名
service1.save();
}
}
重要注意事项
-
引用的 bean 必须在容器中存在:
- 在使用
<property name="bookDao" ref="bookDao"/>时,ref 属性指定的 bean 必须在容器中存在 - 如果引用的 bean 不存在,Spring 容器启动时会抛出异常
- 在使用
-
获取 bean 时的异常处理:
- 无论通过 id 还是 name 获取 bean,如果指定的标识符在容器中不存在,将抛出 NoSuchBeanDefinitionException 异常
- 这意味着在获取 bean 前需要确保配置正确,或者进行适当的异常处理
Bean 的作用范围配置
使用 scope 属性可以配置 bean 的作用范围
<bean id="bookDao" name="dao" class="com.example.dao.impl.BookDaoImpl" scope="prototype"/>
- scope 属性:为 bean 设置作用范围,可选值包括:
- singleton:单例模式(默认值),整个容器中只有一个实例(地址值相同)
- prototype:非单例模式,每次请求都会创建一个新的实例(地址值不同)
验证 IOC 容器中对象是否为单例
通过获取同一个 bean 两次,比较两个对象的地址值来判断是否为单例:
public class AppForScope {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao1 = (BookDao) ctx.getBean("bookDao");
BookDao bookDao2 = (BookDao) ctx.getBean("bookDao");
System.out.println(bookDao1); // 打印对象地址
System.out.println(bookDao2); // 打印对象地址
}
}
验证结果:
结论:默认情况下,两个对象打印的地址值相同,证明 Spring 创建的 bean 对象都是单例的。
Scope 使用的重要思考
- 为什么 Bean 默认为单例?
- 性能优化:单例模式避免了对象的频繁创建与销毁,实现了对象的复用
- 资源节约:减少了内存开销和垃圾回收压力
- 适用场景:对于无状态的服务对象,单例模式是最佳选择
- Bean 在容器中是单例的,会不会产生线程安全问题?
- 有状态对象:如果对象有成员变量用于存储数据,所有请求线程共享同一个 bean 实例,会存在线程安全问题
- 无状态对象:如果对象没有成员变量存储数据(仅提供方法),方法中的局部变量在调用结束后会被销毁,不会存在线程安全问题
- 哪些 Bean 对象适合交给容器管理?
- 表现层对象(Controller)
- 业务层对象(Service)
- 数据层对象(Dao/Repository)
- 工具对象(Utils)
- 哪些 Bean 对象不适合交给容器管理?
- 封装实例的域对象(如 Entity、DTO、VO 等)
- 原因:这些对象通常包含状态信息,如果作为单例管理会引发线程安全问题
Bean 实例化
核心问题: 对象已经能交给Spring的IOC容器来创建了,但是容器是如何来创建对象的呢?
本节旨在研究 Bean的实例化过程,主要解决两部分内容:
- Bean是如何创建的
- 实例化Bean的三种方式:构造方法、静态工厂和实例工厂(FactoryBean)
基本认知: Bean本质上就是对象。对象在Java中通常通过new关键字调用构造方法创建,因此Spring创建Bean本质上也是通过构造方法完成的。我们将基于此来验证Spring中Bean的三种创建方式。
学习目标: 这些方式中,重点掌握构造方法和FactoryBean即可。
环境准备
建立项目结构的步骤 同前面 IOC和DI入门案例-代码实现 的都一致
构造方法实例化
这是Spring中最基本和最常用的Bean创建方式。
创建验证与原理测试
步骤 1: 准备需要被创建的类
创建接口BookDao及其实现类BookDaoImpl。
// BookDao.java
public interface BookDao {
public void save();
}
// BookDaoImpl.java
public class BookDaoImpl implements BookDao {
// 提供一个无参构造函数(可选,Java默认提供)
public BookDaoImpl() {
System.out.println("BookDaoImpl constructor is running ....");
}
@Override
public void save() {
System.out.println("book dao save ...");
}
}
步骤 2: 将类配置到Spring容器
在applicationContext.xml中使用<bean>标签进行配置。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
</beans>
步骤 3: 编写运行程序
创建应用程序入口类,获取Spring容器(IOC容器)并从中获取配置好的Bean。
// AppForInstanceBook.java
public class AppForInstanceBook {
public static void main(String[] args) {
// 1. 加载配置文件,初始化IOC容器(ApplicationContext是容器接口)
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2. 从容器中根据id获取Bean实例
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
// 3. 使用Bean
bookDao.save();
}
}
运行结果:
结论: 从输出结果可以看到,Spring容器在创建bookDao这个Bean时,自动调用了BookDaoImpl类的无参构造方法。这证实了Spring默认使用构造方法来实例化Bean。
测试 1: 将构造方法改为 private
修改BookDaoImpl类的构造方法访问权限为private。
public class BookDaoImpl implements BookDao {
// 私有构造方法
private BookDaoImpl() {
System.out.println("book dao constructor is running .... (Private)");
}
public void save() {
System.out.println("book dao save ...");
}
}
运行结果: 程序依然成功执行,输出相同的结果。
结论: Spring能够成功调用类的私有构造方法。这证明Spring底层并非简单地使用new关键字,而是利用了反射(Reflection) 机制来突破Java的访问权限限制,从而实例化对象。
测试 2: 构造方法中添加一个参数
修改BookDaoImpl类,提供一个需要int类型参数的构造方法,并不提供无参构造。
public class BookDaoImpl implements BookDao {
// 需要一个int参数的构造方法
public BookDaoImpl(int i) {
System.out.println("book dao constructor with param is running ....");
}
public void save() {
System.out.println("book dao save ...");
}
}
运行结果: 此时运行程序会抛出异常BeanCreationException,提示找不到合适的默认构造方法。
核心结论
- Spring IoC容器实例化Bean的首选且最常用方式是通过构造方法
- 其底层实现机制是反射。配置
<bean>标签的class属性后,容器会使用反射加载指定的类,并默认调用该类的无参构造方法来创建对象实例 - 即使该构造方法是
private的,反射机制也能正常工作。 - 如果类中没有显式提供任何构造方法,Java编译器会默认提供一个无参构造,Spring即可使用它
- 如果类中显式定义了带参数的构造方法,就必须同时提供一个无参构造方法,否则Spring将无法直接通过此默认方式实例化该Bean,后续需要通过工厂方法或在配置中指定构造参数等其他方式来解决。
静态工厂实例化
普通静态工厂创建Bean
在了解Spring的静态工厂实例化之前,我们先回顾如何使用普通的静态工厂来创建对象:
(1) 准备接口和实现类
创建OrderDao接口及其实现类OrderDaoImpl。
// OrderDao.java
public interface OrderDao {
public void save();
}
// OrderDaoImpl.java
public class OrderDaoImpl implements OrderDao {
@Override
public void save() {
System.out.println("order dao save ...");
}
}
(2) 创建静态工厂类
创建一个工厂类OrderDaoFactory,并提供静态方法getOrderDao()。
// OrderDaoFactory.java
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
return new OrderDaoImpl();
}
}
(3) 编写运行类
编写AppForInstanceOrder类,通过静态工厂获取对象。
// AppForInstanceOrder.java
public class AppForInstanceOrder {
public static void main(String[] args) {
// 通过静态工厂创建对象
OrderDao orderDao = OrderDaoFactory.getOrderDao();
orderDao.save();
}
}
(4) 运行结果
order dao save ...
现在的问题是:如何将这种通过工厂创建对象的方式交给Spring来管理?
Spring静态工厂创建Bean
将静态工厂创建对象的方式整合到Spring中的具体步骤如下:
(1) 修改工厂类
在工厂方法中添加必要的业务操作,展示静态工厂的独特优势。
// OrderDaoFactory.java
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
System.out.println("factory setup...."); // 模拟必要的业务操作
return new OrderDaoImpl();
}
}
(2) 配置Spring配置文件
在applicationContext.xml中添加Bean配置。
<bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao"/>
- class: 指定工厂类的全限定名
- factory-method: 指定工厂类中创建对象的静态方法名
配置关系图示:\
(3) 修改运行类
改为从Spring IOC容器中获取Bean。
// AppForInstanceOrder.java
public class AppForInstanceOrder {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
orderDao.save();
}
}
(4) 运行结果
factory setup....
order dao save ...
静态工厂实例化的意义
可能会有人疑问:工厂类中最终还是通过new创建对象,与直接使用构造方法实例化相比似乎更复杂,这种方式的意义是什么?
关键优势在于:在工厂的静态方法中,除了创建对象,还可以执行必要的业务操作(如初始化配置、日志记录、权限检查等),这些操作在直接new对象时是无法实现的。
应用场景:这种方式主要用于兼容早期的一些老系统,在现代Spring开发中了解即可,实际应用中较少使用。
实例工厂实例化与FactoryBean
普通实例工厂创建Bean
(1) 准备接口和实现类
创建UserDao接口及其实现类UserDaoImpl。
// UserDao.java
public interface UserDao {
public void save();
}
// UserDaoImpl.java
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("user dao save ...");
}
}
(2) 创建实例工厂类
创建一个工厂类UserDaoFactory,提供一个普通方法(注意:与静态工厂不同,这里不是静态方法)。
// UserDaoFactory.java
public class UserDaoFactory {
public UserDao getUserDao(){
return new UserDaoImpl();
}
}
(3) 编写运行类测试
编写AppForInstanceUser类,通过实例工厂获取对象。
// AppForInstanceUser.java
public class AppForInstanceUser {
public static void main(String[] args) {
// 创建实例工厂对象
UserDaoFactory userDaoFactory = new UserDaoFactory();
// 通过实例工厂对象创建UserDao
UserDao userDao = userDaoFactory.getUserDao();
userDao.save();
}
}
(4) 运行结果
user dao save ...
现在的问题是:如何将这种实例工厂创建对象的方式交给Spring来管理?
Spring实例工厂创建Bean
将实例工厂创建对象的方式整合到Spring中的具体配置:
(1) 配置Spring配置文件
在applicationContext.xml中添加以下配置:
<!-- 1. 先创建工厂实例 -->
<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
<!-- 2. 通过工厂实例创建目标对象 -->
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
配置解析:
- 第一行:创建工厂实例,id为"userFactory"
- 第二行:通过工厂实例创建目标Bean
factory-bean="userFactory": 引用容器中已定义的id为"userFactory"的Bean作为工厂实例factory-method="getUserDao": 指定在"userFactory"这个工厂实例上要调用的方法名为"getUserDao"
配置关系图示:\
(2) 修改运行类
改为从Spring IOC容器中获取Bean。
// AppForInstanceUser.java
public class AppForInstanceUser {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) ctx.getBean("userDao");
userDao.save();
}
}
(3) 运行结果
user dao save ...
实例工厂实例化的配置相对复杂,为了简化这种配置方式,Spring提供了FactoryBean接口。
FactoryBean简化配置
FactoryBean是Spring提供的一种更简洁的实现工厂模式的方式。
(1) 创建FactoryBean实现类
创建一个实现FactoryBean接口的类UserDaoFactoryBean。
// UserDaoFactoryBean.java
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
// 代替原始实例工厂中创建对象的方法
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
// 返回所创建类的Class对象
public Class<?> getObjectType() {
return UserDao.class;
}
// 可选:设置是否为单例,默认true
public boolean isSingleton() {
return true; // 默认单例,可根据需要返回false
}
}
(2) 简化Spring配置
在配置文件中只需配置FactoryBean本身。
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean"/>
(3) 运行测试
运行类不需要任何修改,直接运行即可得到相同结果。
FactoryBean接口的三个方法:
T getObject():必须被重写,在此方法中实现创建Bean实例的逻辑并返回该实例。Class<?> getObjectType():必须被重写,返回由getObject()方法创建的Bean的类型(Class对象)。default boolean isSingleton():可选重写,返回Bean是否为单例模式,默认true(true,单例模式;false,非单例模式)
FactoryBean方式在Spring整合其他框架时会被广泛使用,是需要重点理解和掌握的内容。一般情况下,我们保持默认的单例模式即可,无需重写
isSingleton()方法。
Bean的生命周期
- 生命周期:从创建到消亡的完整过程。
- Bean生命周期:Bean对象从创建到销毁的整体过程。
通过理解和控制Bean的生命周期,我们可以在Bean创建后到销毁前以及创建和销毁的关键时刻执行必要的操作,如资源初始化、连接池管理、缓存清理等,从而提高应用程序的健壮性和性能。
环境准备
建立项目结构 和 初始代码 同前面 IOC和DI入门案例-代码实现 的都一致
生命周期控制(初始化和销毁方法)
概述
在Spring框架中,我们可以对Bean的生命周期进行干预,特别是在两个关键阶段:
- Bean创建之后:用于执行初始化操作,如初始化资源
- Bean销毁之前:用于执行清理操作,如释放资源
Spring提供了两种方式来实现生命周期控制:
- 通过XML配置文件指定方法
- 通过实现Spring接口
配置方式实现
实现步骤
步骤1:添加初始化和销毁方法
在Bean的实现类中创建两个方法,方法名可任意定义。
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
// Bean初始化时执行的操作
public void init() {
System.out.println("init...");
}
// Bean销毁前执行的操作
public void destory() {
System.out.println("destory...");
}
}
步骤2:配置生命周期方法
在Spring配置文件中,为需要配置生命周期的 bean 使用 init-method(指定初始化方法名) 和 destroy-method(指定销毁方法名) 属性指定相应的方法。
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"
init-method="init"
destroy-method="destory"/>
步骤3:运行测试
创建测试类验证生命周期方法(注意添加容器关闭的操作):
public class AppForLifeCycle {
public static void main(String[] args) {
// 需要使用ClassPathXmlApplicationContext才能调用close方法
ClassPathXmlApplicationContext ctx =
new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
// 直接调用close()方法关闭容器,触发destroy方法
ctx.close();
}
}
运行AppForLifeCycle打印结果为:
从结果中可以看出,init方法执行了,但是destroy方法却未执行,这是因为容器需要手动关闭。
容器关闭的两种方式
需要手动关闭容器的原因:
Spring 的 ApplicationContext 接口实现(如 ClassPathXmlApplicationContext)负责管理 Bean 的生命周期。main 方法执行完毕后 JVM 立即退出,容器没有机会执行其正常的关闭流程,导致所有配置了 销毁方法destroy-method 的 Bean 都无法执行资源清理操作。
解决方法:由于ApplicationContext接口没有提供关闭方法,需要先创建其实现类ClassPathXmlApplicationContext实例,并提供两种关闭方式:
- 创建其实现类ClassPathXmlApplicationContext实例:
ClassPathXmlApplicationContext bean对象名 = new ClassPathXmlApplicationContext("applicationContext.xml");
方式一:直接关闭容器:bean对象名.close();
// 创建ClassPathXmlApplicationContext实例
ClassPathXmlApplicationContext ctx =
new ClassPathXmlApplicationContext("applicationContext.xml");
// 执行业务逻辑...
// 直接调用close()方法关闭容器
ctx.close();
方式二:注册钩子关闭容器:bean对象名.registerShutdownHook();
// 创建ClassPathXmlApplicationContext实例
ClassPathXmlApplicationContext ctx =
new ClassPathXmlApplicationContext("applicationContext.xml");
// 执行业务逻辑...
// 注册JVM关闭钩子,在JVM退出前自动关闭容器
ctx.registerShutdownHook();
两种关闭方式的对比
| 方式 | 执行时机 | 适用场景 |
|---|---|---|
| close() | 执行到close()方法时立即关闭容器 | 需要精确控制容器关闭时机 |
| registerShutdownHook() | JVM退出前自动调用关闭 | 希望自动清理资源,无需手动控制 |
注意:
prototype作用域的 Bean,其销毁方法需要由客户端自行管理,Spring 容器在关闭时不会调用它们的destroy-method。
接口方式实现
Spring提供了两个接口来实现生命周期控制,无需XML配置。
实现方式
// 1.导入必要的Spring接口
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
// 2.业务接口实现类,同时实现Spring生命周期接口
public class BookServiceImpl implements BookService,
InitializingBean, // 初始化接口,用于Bean初始化后的回调
DisposableBean { // 销毁接口,用于Bean销毁前的回调
private BookDao bookDao;
public void setBookDao(BookDao bookDao) { // Setter方法用于依赖注入
System.out.println("执行setBookDao方法: 依赖注入完成");
this.bookDao = bookDao;
}
public void save() { // 业务方法
System.out.println("执行save方法: 业务逻辑处理");
bookDao.save();
}
// 3.实现DisposableBean接口的destroy方法
// 这个方法会在Bean销毁前被Spring容器自动调用
public void destroy() throws Exception {
System.out.println("执行destroy方法: 释放资源、关闭连接等清理操作");
}
// 4.实现InitializingBean接口的afterPropertiesSet方法
// 这个方法会在所有属性设置完成后被Spring容器自动调用
public void afterPropertiesSet() throws Exception {
System.out.println("执行afterPropertiesSet方法: 初始化资源、加载配置等准备操作");
}
}
注意:初始化方法
afterPropertiesSet(),翻译过来为属性设置之后。 初始化方法会在类中属性设置完毕之后执行
总结对比
两种实现方式对比
| 特性 | 配置方式 | 接口方式 |
|---|---|---|
| 耦合度 | 低(与Spring解耦) | 高(需要实现Spring接口) |
| 配置量 | 需要在XML中配置 | 无需XML配置 |
| 灵活性 | 方法名可任意定义 | 必须使用特定方法名 |
推荐使用配置方式,降低代码与Spring框架的耦合度
生命周期方法的执行顺序
完整示例输出结果:
set method executed
service init
book service save ...
book dao save ...
service destroy
destory...
此输出结果验证了生命周期方法的执行顺序:
- 属性注入(set方法)
- 初始化方法(afterPropertiesSet)
- 业务方法执行
- 销毁方法(destroy)
DI相关内容
依赖注入(Dependency Injection,DI)描述了在 Spring 容器中建立 bean 与 bean 之间依赖关系的过程。
Spring 主要提供了两种注入方式:
- Setter 注入
- 构造器注入
这两种方式均可用于注入以下类型:
- 引用类型(对象依赖)
- 简单类型(基本数据类型与 String)
Setter 注入
Setter 注入是通过调用类的 setter 方法来实现依赖注入的方式。
环境准备
建立项目结构 和 初始代码 同前面 IOC和DI入门案例-代码实现 的基本一致。
额外添加一个UserDao接口及其实现类:
// UserDao.java
public interface UserDao {
void save();
}
// UserDaoImpl.java
public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("user dao save ...");
}
}
引用类型注入(ref 属性)
前面 IOC和DI入门案例 使用的注入方式就是 Setter注入 引用类型(对象依赖)
多个引用类型注入
与下面的 多个简单数据类型注入(value 属性) 中的方式同理,只是类型不同:多个 引用类型的setter方法、多个 引用类型的<property>标签。
多个简单数据类型注入(value 属性)
Setter 注入不仅可以用于引用类型,也可以用于简单数据类型(基本数据类型和 String 类型)。
- 声明属性并提供 Setter 方法
在目标类中声明简单数据类型的属性,并提供对应的 setter 方法:
public class BookDaoImpl implements BookDao {
private String databaseName;
private int connectionNum;
// 为简单数据类型属性提供 setter 方法
public void setConnectionNum(int connectionNum) {
this.connectionNum = connectionNum;
}
public void setDatabaseName(String databaseName) {
this.databaseName = databaseName;
}
@Override
public void save() {
System.out.println("book dao save ..." + databaseName + ", " + connectionNum);
}
}
- 配置文件中的注入配置
在 Spring 配置文件中使用 property 标签的 value 属性注入简单数据类型:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
<!-- 注入简单数据类型使用 value 属性 -->
<property name="databaseName" value="mysql"/>
<property name="connectionNum" value="10"/>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<!-- 注入引用数据类型使用 ref 属性 -->
<property name="bookDao" ref="bookDao"/>
<property name="userDao" ref="userDao"/>
</bean>
</beans>
- 运行测试
创建测试类验证:
public class AppForDISet {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
运行AppForDISet类,查看结果,说明userDao已经成功注入:
类型转换:
Spring 会对value属性值自动进行类型转换,但必须确保值的格式正确
- ✅ 正确:
<property name="connectionNum" value="10"/>- ❌ 报错:
<property name="connectionNum" value="abc"/>(无法将"abc"转换为 int)
构造器注入
构造器注入 使用 <constructor-arg> 标签配置依赖。
<constructor-arg> 标签的name 属性必须与构造方法参数名保持一致。
当类中存在多个重载的构造方法时,Spring 会优先尝试匹配参数数量最多且所有参数都能在IOC容器中找到对应bean依赖的构造方法。
环境准备
建立项目结构 和 初始代码 同前面 IOC和DI入门案例-代码实现 的基本一致。
额外添加一个UserDao接口及其实现类:
// UserDao.java
public interface UserDao {
void save();
}
// UserDaoImpl.java
public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("user dao save ...");
}
}
引用类型注入
- 将 Setter方法 替换为 构造方法 以注入
在 BookServiceImpl 类中删除 bookDao 的 setter 方法,并添加带有 bookDao 参数的构造方法:
public class BookServiceImpl implements BookService {
private BookDao bookDao;
// 构造方法注入
public BookServiceImpl(BookDao bookDao) {
this.bookDao = bookDao;
}
@Override
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
- 修改配置文件使用构造器注入
在 applicationContext.xml 中使用 <constructor-arg> 标签配置构造器注入:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<!-- 使用 constructor-arg 标签进行构造器注入 -->
<constructor-arg name="bookDao" ref="bookDao"/>
</bean>
</beans>
多个引用类型注入
与下面的 多个简单数据类型注入(value 属性) 中的方式同理,只是类型不同:多个 引用类型参数的构造方法、多个 引用类型的<constructor-arg>标签。
多个简单数据类型注入
- 添加多个简单属性并提供构造方法
修改BookDaoImpl类,添加多个参数的构造方法
public class BookDaoImpl implements BookDao {
private String databaseName;
private int connectionNum;
public BookDaoImpl(String databaseName, int connectionNum) {
this.databaseName = databaseName;
this.connectionNum = connectionNum;
}
public void save() {
System.out.println("book dao save ..."+databaseName+","+connectionNum);
}
}
- 配置完成多个属性构造器注入
在applicationContext.xml中进行注入配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
<constructor-arg name="databaseName" value="mysql"/>
<constructor-arg name="connectionNum" value="666"/>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
<constructor-arg name="userDao" ref="userDao"/>
</bean>
</beans>
注入方式选择
根据Spring官方和实际开发经验,我们可以总结出以下准则:
- Spring框架倡导使用构造器注入,因为这样可以保证依赖不可变(immutable)并确保完全初始化的对象。
- 强制依赖:使用构造器注入。这样能保证对象在创建时就具有所有必需的依赖,避免null对象出现。
强制依赖指的是对象在创建时必须提供的参数。 - 可选依赖:使用Setter注入。这样可以在对象创建后动态地改变依赖,灵活性强。(可选依赖指的是对象在创建时不一定需要,可以在后期设置的参数)
- 如果有必要,可以同时使用两种注入方式 :用构造器注入来完成强制依赖的注入,用Setter注入来完成可选依赖的注入。
- 实际开发中还要根据实际情况分析。如果受控对象(即我们要注入的Bean)没有提供setter方法,那么就必须使用构造器注入。
- 自己开发的模块,如果团队没有强制要求,可以根据需要选择。但推荐使用构造器注入来保证依赖的不可变性和避免null指针。
自动配置
概述
依赖自动装配 是指 Spring IoC 容器根据 bean 所依赖的资源,在容器中自动查找并注入到 bean 中的过程。
减少了手动配置的工作量,方便维护XML配置文件,提高了开发效率。
自动装配方式
Spring 提供了以下几种自动装配方式:
按类型自动装配 (byType)
- 工作原理:Spring 容器会查找与需要注入的属性类型相匹配的 bean
- 适用场景:当容器中只有一个该类型的 bean 时
- 配置方式:
autowire="byType" - 特点:
- 最常用的自动装配方式
- 如果有多个相同类型的 bean,会抛出异常
NoUniqueBeanDefinitionException - 如果没有找到匹配类型的bean,不会报错,而是保留属性为
null
- 运行逻辑流程:
- 扫描bean的所有setter方法
- 获取setter方法的参数类型
- 在容器中查找匹配该类型的bean
- 如果找到唯一匹配则注入,多个匹配报错
按名称自动装配 (byName)
- 工作原理:Spring 容器会查找与需要注入的属性名称相匹配的 bean ID
- 适用场景:当需要精确指定要注入的 bean 时
- 配置方式:
autowire="byName" - 特点:
- 通过属性名与 bean ID 的匹配进行注入
- 不会出现多个匹配的情况
- 运行逻辑流程:
- 扫描bean的所有setter方法
- 从setter方法名提取属性名(去掉"set"前缀)
- 在容器中查找bean id匹配属性名的bean
- 找到则注入,找不到则跳过
按构造方法自动装配 (constructor)
- 工作原理:类似于 byType,但是应用于构造器参数
- 适用场景:构造器注入的自动配置
- 配置方式:
autowire="constructor" - 特点:
- 根据构造方法的参数类型进行自动装配
- 如果找不到匹配的 bean,会抛出异常
- 运行逻辑流程:
- 查找bean的所有构造方法
- 按参数数量降序排序(Spring默认策略)
- 遍历排序后的构造方法,从参数最多的开始尝试
- 对每个构造参数按类型在容器中查找匹配bean
- 如果所有参数都能找到唯一匹配的bean → 使用该构造方法
- 如果有参数找不到匹配或多个匹配 → 立即放弃该构造方法
- 继续尝试参数次多的构造方法,重复步骤4
- 如果所有构造方法都无法满足 → 抛出
BeanCreationException
不启用自动装配 (no)
- 说明:默认值,不进行自动装配,需要手动配置所有依赖
- 配置方式:
autowire="no"或省略该属性
配置示例 与 autowire属性位置
自动装配的配置方式 autowire属性 写在 接受自动装配依赖 的bean标签中。
一般配置方式
<!-- 被依赖的bean(提供者) -->
<!-- 按类型自动装配 -->
<bean id="bookService" class="com.example.BookServiceImpl" autowire="byType"/>
<!-- 按名称自动装配 -->
<bean id="bookService" class="com.example.BookServiceImpl" autowire="byName"/>
<!-- 按构造方法自动装配 -->
<bean id="bookService" class="com.example.BookServiceImpl" autowire="constructor"/>
<!-- 不启用自动装配(默认) -->
<bean id="bookService" class="com.example.BookServiceImpl" autowire="no"/>
<!-- 接受自动装配的bean(消费者)- autowire写在这里 -->
<bean id="bookService" class="com.example.BookServiceImpl" autowire="byType">
<!-- 可以同时配置简单类型属性 -->
<property name="timeout" value="5000"/>
</bean>
全局默认自动装配(default-autowire)
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"
default-autowire="byType"> <!-- 设置全局默认自动装配方式,byType可换其他自动装配方式值 -->
<!-- 所有bean默认使用byType自动装配 -->
<bean id="bookService" class="com.example.BookServiceImpl"/>
<bean id="userService" class="com.example.UserServiceImpl"/>
<!-- 覆盖全局设置:不使用自动装配 -->
<bean id="specialBean" class="com.example.SpecialService" autowire="no"/>
<!-- 覆盖全局设置:使用不同的自动装配方式 -->
<bean id="paymentService" class="com.example.PaymentServiceImpl" autowire="byName"/>
</beans>
与其他配置方式的结合
自动装配可以与其他配置方式结合使用:
下面的代码示例展示了一些良好实践:
autowire="byType":自动处理复杂的对象依赖关系<property>手动配置:明确设置简单的配置参数- 同时使用两种配置方式,手动配置的优先级更高,会覆盖自动装配对同一属性的设置。
示例:
BookServiceImpl 类的结构如下:
public class BookServiceImpl {
// 引用类型依赖 - 通过 autowire="byType" 自动注入
private BookRepository bookRepository; // 自动注入
// 简单类型配置 - 通过 <property> 手动配置
private boolean cacheEnabled; // 手动配置
private int timeout; // 手动配置
// 演示属性:会被自动装配和手动配置两种方式设置
private BookRepository backupRepository; // 演示覆盖特性
// setter 方法...
public void setBackupRepository(BookRepository backupRepository) {
this.backupRepository = backupRepository;
}
// ... 其他setter方法
}
xml配置
<!-- 自动装配的依赖bean -->
<bean id="bookRepository" class="com.example.JdbcBookRepository"/>
<bean id="backupRepository" class="com.example.BackupBookRepository"/> <!-- 自动装配会注入这个 -->
<!-- 主要服务bean - 混合配置 -->
<bean id="bookService" class="com.example.BookServiceImpl" autowire="byType">
<!-- 配置简单类型参数 -->
<property name="cacheEnabled" value="true"/>
<property name="timeout" value="5000"/>
<!-- 手动配置会覆盖自动装配 -->
<property name="backupRepository" ref="bookRepository"/> <!-- 最终使用这个,覆盖自动装配的backupRepository -->
</bean>
在实际项目中,服务类通常需要:
- 外部依赖(其他服务、仓库、数据源等)- 适合自动装配
- 配置参数(超时时间、特性开关、环境标识等)- 适合手动配置
实践建议
推荐使用场景
- 中小型项目:使用 byType 自动装配提高开发效率
- 原型开发:快速搭建和验证业务逻辑
- 测试环境:简化测试配置
不推荐使用场景
- 大型复杂系统:依赖关系复杂,需要显式配置保证清晰性
- 多模块项目:不同模块可能有相同类型的 bean
- 需要精确控制的场景:某些依赖需要特殊处理或包装
注意事项
注意:
- 自动装配用于引用类型依赖注入,不能对简单类型进行操作
- 自动装配无法处理集合类型的依赖(List、Map、Set等)
- 实际开发中优先使用按类型装配,使用按类型装配时(byType)必须保障容器中相同类型的bean唯一(对于相同类型的多个bean,需要配合
@Primary、@Qualifier或显式配置使用) - 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用
- 显式配置(
<property>/<constructor-arg>)的优先级高于自动装配,同时出现时自动装配配置失效 - 缺点:自动装配会降低配置的显式性,影响代码可读性;依赖关系不明确时,调试会更加困难
集合注入
概述
在 Spring 框架中,集合注入是指将数组、List、Set、Map、Properties 等集合类型作为依赖注入到 Bean 中的过程。集合中可以包含简单数据类型(String、Integer等)或引用数据类型(其他 Bean)。
集合类型分类
| 集合类型 | 特点 | 适用场景 |
|---|---|---|
| 数组 | 固定大小,有序,可重复 | 需要固定数量元素的场景 |
| List | 动态大小,有序,可重复 | 需要有序且允许重复的集合 |
| Set | 动态大小,无序,不可重复 | 需要元素唯一性的场景 |
| Map | 键值对集合 | 需要通过键快速查找值的场景 |
| Properties | 字符串键值对,继承自Hashtable | 配置参数、属性文件 |
环境准备
建立项目结构 和 初始代码 同前面 IOC和DI入门案例-代码实现 的基本一致。
核心类实现
BookDao.java
public interface BookDao {
public void save();
}
BookDaoImpl.java
public class BookDaoImpl implements BookDao {
private int[] array;
private List<String> list;
private Set<String> set;
private Map<String, String> map;
private Properties properties;
public void save() {
System.out.println("book dao save ...");
System.out.println("遍历数组:" + Arrays.toString(array));
System.out.println("遍历List:" + list);
System.out.println("遍历Set:" + set);
System.out.println("遍历Map:" + map);
System.out.println("遍历Properties:" + properties);
}
// Setter方法
public void setArray(int[] array) {
this.array = array;
}
public void setList(List<String> list) {
this.list = list;
}
public void setSet(Set<String> set) {
this.set = set;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
测试类
AppForDICollection.java
public class AppForDICollection {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
集合注入配置详解
基础配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
<!-- 各种集合注入配置将在这里添加 -->
</bean>
</beans>
各种简单数据类型注入示例
数组类型注入
<property name="array">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>
说明: 使用 <array> 标签定义数组,<value> 标签定义数组元素值。
List类型注入
<property name="list">
<list>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>chuanzhihui</value>
</list>
</property>
说明:
使用 <list> 标签定义List集合,元素有序且可重复。
标签混用特性
- List底层通过数组实现,所以
<list>和<array>标签可以混用 - 但强烈不推荐混用,为了语义清晰应该按实际类型使用对应标签
Set类型注入
<property name="set">
<set>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>boxuegu</value> <!-- 重复值会被自动去重 -->
</set>
</property>
说明: 使用 <set> 标签定义Set集合,元素保持元素插入顺序且自动去重。
Map类型注入
<property name="map">
<map>
<entry key="country" value="china"/>
<entry key="province" value="henan"/>
<entry key="city" value="kaifeng"/>
</map>
</property>
说明: 使用 <map> 标签定义Map集合,<entry> 标签定义键值对。
Properties类型注入
<property name="properties">
<props>
<prop key="country">china</prop>
<prop key="province">henan</prop>
<prop key="city">kaifeng</prop>
</props>
</property>
说明: Properties的键和值都必须是String类型,使用 <props> 标签定义Properties,<prop> 标签定义属性。
引用类型注入示例
集合中注入引用类型时,将 <value> 标签改为 <ref> 标签:
<property name="serviceList">
<list>
<ref bean="userService"/>
<ref bean="orderService"/>
</list>
</property>
示例:
<!-- 需要定义被引用的bean -->
<bean id="userService" class="com.example.UserServiceImpl"/>
<bean id="orderService" class="com.example.OrderServiceImpl"/>
<bean id="bookService" class="com.example.BookServiceImpl">
<property name="serviceList">
<list>
<ref bean="userService"/> <!-- 引用已定义的bean -->
<ref bean="orderService"/> <!-- 引用已定义的bean -->
<!-- 也可以内联定义bean -->
<bean class="com.example.SpecialServiceImpl">
<property name="timeout" value="5000"/>
</bean>
</list>
</property>
</bean>
集合数据配置位置
对于集合注入,被注入的集合数据 直接配置在 接受依赖的bean的property标签内部,作为该bean配置的一部分。只有当集合元素本身是其他bean引用时,才需要先定义那些被引用的bean。
特殊情况:集合中包含引用类型
当集合中需要包含其他bean引用时,配置方式稍有不同:
<!-- 先定义被引用的bean -->
<bean id="service1" class="com.example.ServiceImpl1"/>
<bean id="service2" class="com.example.ServiceImpl2"/>
<!-- 在集合中使用ref引用外部bean -->
<bean id="bookService" class="com.example.BookServiceImpl">
<property name="serviceList">
<list>
<ref bean="service1"/> <!-- 引用外部bean -->
<ref bean="service2"/> <!-- 引用外部bean -->
</list>
</property>
</bean>
完整配置示例
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
<!-- 数组注入 -->
<property name="array">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>
<!-- List注入 -->
<property name="list">
<list>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>chuanzhihui</value>
</list>
</property>
<!-- Set注入 -->
<property name="set">
<set>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>boxuegu</value>
</set>
</property>
<!-- Map注入 -->
<property name="map">
<map>
<entry key="country" value="china"/>
<entry key="province" value="henan"/>
<entry key="city" value="kaifeng"/>
</map>
</property>
<!-- Properties注入 -->
<property name="properties">
<props>
<prop key="country">china</prop>
<prop key="province">henan</prop>
<prop key="city">kaifeng</prop>
</props>
</property>
</bean>
</beans>
运行结果
运行 AppForDICollection 类,控制台输出如下:
book dao save ...
遍历数组:[100, 200, 300]
遍历List:[itcast, itheima, boxuegu, chuanzhihui]
遍历Set:[itcast, itheima, boxuegu] <!-- 注意:重复的boxuegu被去重 -->
遍历Map:{country=china, province=henan, city=kaifeng}
遍历Properties:{country=china, province=henan, city=kaifeng}
重要说明
注入方式
- setter注入:使用
<property>标签 - 构造器注入:使用
<constructor-arg>标签,内部同样可以使用集合标签 - 一个
<property>或<constructor-arg>标签内,只能包含一种集合类型标签来定义该属性或构造器参数的值(例外:集合内嵌套集合是允许的)
注意事项
- 类型匹配:确保集合的泛型类型与注入值类型一致
- Setter方法:必须提供对应的setter方法
- 集合去重:Set类型会自动去除重复元素
- 顺序保持:List保持元素顺序,Set不保证顺序