【Spring】2—IOC容器
⭐⭐⭐⭐⭐⭐
Github主页👉https://github.com/A-BigTree
笔记链接👉https://github.com/A-BigTree/Code_Learning
⭐⭐⭐⭐⭐⭐
如果可以,麻烦各位看官顺手点个star~😊
如果文章对你有所帮助,可以点赞👍收藏⭐支持一下博主~😆
文章目录
- 2 IOC容器
- 2.1 IOC容器的概念
- 2.1.1 组件的概念
- 原生Web应用的组件
- SSM整合组件
- 组件管理
- 2.1.2 容器的概念
- 普通容器
- 复杂容器
- 2.1.3 IOC和DI
- 获取资源的传统方式
- 反转控制获取资源
- DI
- 2.1.4 IOC在Spring中的实现
- `BeanFactory`接口
- `ApplicationContext`接口
- `ApplicationContext`的主要实现类
- 2.2 基于XML管理bean
- 2.2.1 创建bean
- 目标和思路
- 创建Maven Module引入依赖
- 创建组件类
- 创建Spring配置文件
- 创建测试类
- 用IOC容器创建对象
- 2.2.2 获取bean
- 根据id获取
- 根据类型获取
- 思考
- 2.2.3 setter注入
- 组件类添加属性
- 配置中指定属性
- 测试
- 2.2.4 引用外部声明bean
- 声明新的组件
- 配置新组件bean
- 原组件bean中引用新组件的bean
- 测试
- 2.2.5 内部bean
- 配置原组件
- 测试
- 2.2.6 引入外部属性文件
- 加入依赖
- 创建外部属性文件
- 在Spring中引入
- 使用
- 测试
- 2.2.7 级联属性赋值
- 配置关联对象的bean
- 装配关联对象并赋值级联属性
- 测试
- 2.2.8 构造器注入
- 声明组件类
- 配置
- 测试
- 补充
- 2.2.9 特殊值处理
- 声明类
- null值
- xml实体
- CDATA节
- 2.2.10 使用`p`名称空间
- 配置
- 测试
- 2.2.11 集合属性
- 配置
- 测试
- 其他变化形式
- 2.2.12 自动装配
- 声明组件类
- 配置
- 测试
- 2.2.13 集合类型的bean
- 配置
- 测试
- 2.2.14 FactoryBean机制
- 简介
- 实现`FactoryBean`接口
- 配置bean
- 测试获取bean
- 2.2.15 bean的作用域
- 概念
- 配置
- 测试
- 2.2.16 bean的生命周期
- 生命周期清单
- 指定初始化方法和销毁方法
- 配置初始化和销毁方法
- bean后置处理器
- 后置处理器放入容器
- 执行效果
- 2.3 基于注解管理bean
- 2.3.1 标记与扫描
- `@Component`注解标记的普通组件
- `@Controller`注解标记的控制器组件
- `@Service`注解标记的业务逻辑组件
- `@Repository`注解标记的持久化层组件
- 扫描
- 组件的bean ID
- 2.3.2 自动装配
- 自动装配的实现
- 标记在其他位置
- 工作流程
- 佛系装配
- 2.3.3 完全注解开发
- 使用配置类取代配置文件
- 配置类中的配置bean
- 在配置类中自动扫描包
- 2.3.4 整合junit4
- 整合的好处
- 操作
2 IOC容器
2.1 IOC容器的概念
2.1.1 组件的概念
原生Web应用的组件
SSM整合组件
组件管理
其实这里我们强调的仍然是我们自己为了开发业务功能而创建的组件,而除了我们开发的组件,框架内部也有很多组件需要管理。
那么不管是我们开发的还是框架内部的,Spring都可以通过IOC容器的方式对它们进行统一管理和调配。所以Spring在SSM三大框架中起到的是一个基础设置的作用。而IOC容器的作用就是帮助我们管理组件。
那到底什么是组件呢?
我们都知道,一个大型的项目可以包含很多子系统,子系统内又可以划分很多模块。这个层次结构是按照它们之间的逻辑关系划分的。那么从系统功能的具体实现的角度来看,我们可以说每一个功能模块都是由『组件』组合起来而实现的。
2.1.2 容器的概念
普通容器
- 数组
- 集合:List
- 集合:Set
复杂容器
Servlet容器能够管理 Servlet、Filter、Listener这样的组件的一生,所以它是一个复杂容器。我们即将要学习的IOC容器也是一个复杂容器。它们不仅要负责创建组件的对象、存储组件的对象,还要负责调用组件的方法让它们工作,最终在特定情况下销毁组件。
Servlet容器:
名称 | 时机 | 次数 |
---|---|---|
创建对象 | 默认情况:接收到第一次请求 修改启动顺序后:Web应用启动过程中 | 一次 |
初始化操作 | 创建对象之后 | 一次 |
处理请求 | 接收到请求 | 多次 |
销毁操作 | Web应用卸载之前 | 一次 |
Filter生命周期:
生命周期阶段 | 执行时机 | 执行次数 |
---|---|---|
创建对象 | Web应用启动时 | 一次 |
初始化 | 创建对象后 | 一次 |
拦截请求 | 接收到匹配的请求 | 多次 |
销毁 | Web应用卸载前 | 一次 |
2.1.3 IOC和DI
IOC:Inversion of Control
,翻译过来是反转控制。
获取资源的传统方式
在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率。
比如我们在Servlet中自己创建Service对象:
private BookService bookService = new BookServiceImpl();
虽然创建BookServiceImpl
对象不难,但是此时得到的仅仅是一个BookServiceImpl
对象而已;如果我们需要附加事务功能,那就必须自己编写代码,执行事务操作。
反转控制获取资源
反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式。
使用IOC容器后,同样是给BookService属性赋值:
@Autowired
private BookService bookService;
我们只需要声明要什么类型,Spring会自动将我们所需类型的对象注入到这个属性中,而且是已经附加事务操作的、被增强了的BookService
对象。对象的创建和事务的增强都与业务功能的开发过程无关,确实能够大大简化业务代码的编写。所以使用框架就是为了让程序员能够尽可能专注于业务的开发,而不必为重复性的操作分散精力。
当然,我们也必须承认:要实现上面的效果,我们需要搭建环境。所谓搭建环境主要就是在配置文件中做必要的配置。
DI
DI:Dependency Injection
,翻译过来是依赖注入。
DI是IOC的另一种表述方式:即组件以一些预先定义好的方式(例如:setter方法)接受来自于容器的资源注入。相对于IOC而言,这种表述更直接。
所以结论是:IOC就是一种反转控制的思想, 而DI是对IOC的一种具体实现。
起初是把DI看做是IOC的实现来对待,IOC作为一种思想或标准;后来发现基本上IOC只有DI这一种实现方式,所以后来也不做严格区分了,现在我们可以认为IOC和DI是等同的。
2.1.4 IOC在Spring中的实现
Spring的IOC容器就是IOC思想的一个落地的产品实现。IOC容器中管理的组件也叫做bean。在创建bean之前,首先需要创建IOC容器。Spring提供了IOC容器的两种实现方式:
BeanFactory
接口
这是IOC容器的基本实现,是Spring内部使用的接口。面向Spring本身,不提供给开发人员使用。
bean:本意是“豆”,在这里指的是我们IOC容器中管理的各个组件。
factory:本意是“工厂”,在这里符合了23种设计模式中的工厂模式。如果某一个类是专门用来创建特定的对象,那么这样的类我们可以定义为工厂类。
ApplicationContext
接口
BeanFactory
的子接口,提供了更多高级特性。面向Spring的使用者,几乎所有场合都使用ApplicationContext
而不是底层的 BeanFactory
。
以后在 Spring 环境下看到一个类或接口的名称中包含
ApplicationContext
,那基本就可以断定,这个类或接口与 IOC 容器有关。对我们开发人员来说,
ApplicationContext
就是代表整个IOC容器技术体系的顶级接口。
ApplicationContext
的主要实现类
类型名 | 简介 |
---|---|
ClassPathXmlApplicationContext | 通过读取类路径下的XML格式的配置文件创建 IOC 容器对象 |
FileSystemXmlApplicationContext | 通过文件系统路径读取XML格式的配置文件创建 IOC 容器对象 |
ConfigurableApplicationContext | ApplicationContext的子接口,包含一些扩展方法 refresh()和 close() ,让 ApplicationContext具有启动、关闭和刷新上下文的能力。 |
WebApplicationContext | 专门为Web应用准备,基于Web环境创建IOC容器对象,并将对象引入存入 ServletContext域中。 |
2.2 基于XML管理bean
2.2.1 创建bean
目标和思路
由 Spring 的 IOC 容器创建类的对象。
创建Maven Module引入依赖
<dependencies>
<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
</dependencies>
创建组件类
Component
是组件的意思,这里我们创建的这个类,就是我们希望由IOC容器创建对象的类:
package com.atguigu.ioc.component;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class HappyComponent {
public void doWork() {
log.debug("component do work ...");
}
}
创建Spring配置文件
<!-- 实验一 [重要]创建bean -->
<bean id="happyComponent" class="com.atguigu.ioc.component.HappyComponent"/>
bean
标签:通过配置bean标签告诉IOC容器需要创建对象的组件是什么;id
属性:bean的唯一标识;class
属性:组件类的全类名;
创建测试类
public class IOCTest {
// 创建 IOC 容器对象,为便于其他实验方法使用声明为成员变量
private ApplicationContext iocContainer = new ClassPathXmlApplicationContext("applicationContext.xml");
@Test
public void testExperiment01() {
// 从 IOC 容器对象中获取bean,也就是组件对象
HappyComponent happyComponent = (HappyComponent) iocContainer.getBean("happyComponent");
happyComponent.doWork();
}
}
Spring底层默认通过反射技术调用组件类的 无参构造器 来创建组件对象,这一点需要注意。如果在需要无参构造器时,没有无参构造器,则会抛出异常。
用IOC容器创建对象
在Spring环境下能够享受到的所有福利,都必须通过IOC容器附加到组件类上,所以随着我们在Spring中学习的功能越来越多,IOC 容器创建的组件类的对象就会比自己new的对象强大的越来越多。
2.2.2 获取bean
根据id获取
由于 id 属性指定了 bean 的唯一标识,所以根据 bean 标签的 id 属性可以精确获取到一个组件对象。上个实验中我们使用的就是这种方式。
根据类型获取
指定类型bean唯一:
能够正常获取到
@Test
public void testExperiment02() {
HappyComponent component = iocContainer.getBean(HappyComponent.class);
component.doWork();
}
指定类型bean不唯一:
<!-- 实验一 [重要]创建bean -->
<bean id="happyComponent" class="com.atguigu.ioc.component.HappyComponent"/>
<!-- 实验二 [重要]获取bean -->
<bean id="happyComponent2" class="com.atguigu.ioc.component.HappyComponent"/>
根据类型获取时会抛出异常。
思考
如果组件类实现了接口,根据接口类型可以获取bean吗?
可以,前提是bean唯一
如果一个接口有多个实现类,这些实现类都配置了bean,根据接口类型可以获取bean吗?
不行,因为bean不唯一
根据类型来获取bean时,在满足bean唯一性的前提 下,其实只是看:『对象 instanceof
指定的类型』的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到。
2.2.3 setter注入
组件类添加属性
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class HappyComponent {
private String componentName;
public String getComponentName() {
return componentName;
}
public void setComponentName(String componentName) {
this.componentName = componentName;
}
public void doWork() {
log.debug("component do work ...");
}
}
配置中指定属性
<!-- 实验三 [重要]给bean的属性赋值:setter注入 -->
<bean id="happyComponent3" class="com.atguigu.ioc.component.HappyComponent">
<!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 -->
<!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关) -->
<!-- value属性:指定属性值 -->
<property name="componentName" value="veryHappy"/>
</bean>
测试
@Test
public void testExperiment03() {
HappyComponent happyComponent3 = (HappyComponent) iocContainer.getBean("happyComponent3");
String componentName = happyComponent3.getComponentName();
log.debug("componentName = " + componentName);
}
2.2.4 引用外部声明bean
声明新的组件
public class HappyMachine {
private String machineName;
public String getMachineName() {
return machineName;
}
public void setMachineName(String machineName) {
this.machineName = machineName;
}
}
配置新组件bean
<bean id="happyMachine" class="com.atguigu.ioc.component.HappyMachine">
<property name="machineName" value="makeHappy"/>
</bean>
原组件bean中引用新组件的bean
<bean id="happyComponent4" class="com.atguigu.ioc.component.HappyComponent">
<!-- ref 属性:通过 bean 的 id 引用另一个 bean -->
<property name="happyMachine" ref="happyMachine"/>
</bean>
测试
@Test
public void testExperiment04() {
HappyComponent happyComponent4 = (HappyComponent) iocContainer.getBean("happyComponent4");
HappyMachine happyMachine = happyComponent4.getHappyMachine();
String machineName = happyMachine.getMachineName();
log.debug("machineName = " + machineName);
}
2.2.5 内部bean
配置原组件
<!-- 实验五 [重要]给bean的属性赋值:内部bean -->
<bean id="happyComponent5" class="com.atguigu.ioc.component.HappyComponent">
<property name="happyMachine">
<!-- 在一个 bean 中再声明一个 bean 就是内部 bean -->
<!-- 内部 bean 可以直接用于给属性赋值,可以省略 id 属性 -->
<bean class="com.atguigu.ioc.component.HappyMachine">
<property name="machineName" value="makeHappy"/>
</bean>
</property>
</bean>
测试
@Test
public void testExperiment05() {
HappyComponent happyComponent5 = (HappyComponent) iocContainer.getBean("happyComponent5");
HappyMachine happyMachine = happyComponent5.getHappyMachine();
String machineName = happyMachine.getMachineName();
log.debug("machineName = " + machineName);
}
2.2.6 引入外部属性文件
加入依赖
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.3</version>
</dependency>
<!-- 数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
创建外部属性文件
jdbc.user=root
jdbc.password=atguigu
jdbc.url=jdbc:mysql://192.168.198.100:3306/mybatis-example
jdbc.driver=com.mysql.cj.jdbc.Driver
在Spring中引入
<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
在 IDEA 中引入 Spring 配置文件中名称空间的两种操作方式:
- 在打字标签名的过程中根据提示选择一个正确的名称空间;
- 对于直接复制过来的完整标签,可以在名称空间上点击,然后根据提示引入;
使用
<!-- 实验六 [重要]给bean的属性赋值:引入外部属性文件 -->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
测试
@Test
public void testExperiment06() throws SQLException {
DataSource dataSource = iocContainer.getBean(DataSource.class);
Connection connection = dataSource.getConnection();
log.debug("connection = " + connection);
}
2.2.7 级联属性赋值
配置关联对象的bean
<bean id="happyMachine2" class="com.atguigu.ioc.component.HappyMachine"/>
装配关联对象并赋值级联属性
关联对象:happyMachine
;
级联属性:happyMachine.machineName
;
<!-- 实验七 给bean的属性赋值:级联属性赋值 -->
<bean id="happyComponent6" class="com.atguigu.ioc.component.HappyComponent">
<!-- 装配关联对象 -->
<property name="happyMachine" ref="happyMachine2"/>
<!-- 对HappyComponent来说,happyMachine的machineName属性就是级联属性 -->
<property name="happyMachine.machineName" value="cascadeValue"/>
</bean>
测试
@Test
public void testExperiment07() {
HappyComponent happyComponent6 = (HappyComponent) iocContainer.getBean("happyComponent6");
String machineName = happyComponent6.getHappyMachine().getMachineName();
log.debug("machineName = " + machineName);
}
2.2.8 构造器注入
声明组件类
package com.atguigu.ioc.component;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class HappyTeam {
private String teamName;
private Integer memberCount;
private Double memberSalary;
public HappyTeam(String teamName, Integer memberCount, Double memberSalary) {
this.teamName = teamName;
this.memberCount = memberCount;
this.memberSalary = memberSalary;
}
}
配置
<!-- 实验八 给bean的属性赋值:构造器注入 -->
<bean id="happyTeam" class="com.atguigu.ioc.component.HappyTeam">
<constructor-arg value="happyCorps"/>
<constructor-arg value="10"/>
<constructor-arg value="1000.55"/>
</bean>
测试
@Test
public void testExperiment08() {
HappyTeam happyTeam = iocContainer.getBean(HappyTeam.class);
log.debug("happyTeam = " + happyTeam);
}
补充
constructor-arg
标签还有两个属性可以进一步描述构造器参数:
index
属性:指定参数所在位置的索引(从0开始);name
属性:指定参数名;
2.2.9 特殊值处理
声明类
package com.atguigu.ioc.component;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PropValue {
private String commonValue;
private String expression;
}
null值
<property name="commonValue">
<!-- null标签:将一个属性值明确设置为null -->
<null/>
</property>
xml实体
<!-- 实验九 给bean的属性赋值:特殊值处理 -->
<bean id="propValue" class="com.atguigu.ioc.component.PropValue">
<!-- 小于号在XML文档中用来定义标签的开始,不能随便使用 -->
<!-- 解决方案一:使用XML实体来代替 -->
<property name="expression" value="a < b"/>
</bean>
CDATA节
<!-- 实验九 给bean的属性赋值:特殊值处理 -->
<bean id="propValue" class="com.atguigu.ioc.component.PropValue">
<property name="expression">
<!-- 解决方案二:使用CDATA节 -->
<!-- CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据 -->
<!-- XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析 -->
<!-- 所以CDATA节中写什么符号都随意 -->
<value><![CDATA[a < b]]></value>
</property>
</bean>
2.2.10 使用p
名称空间
配置
使用p
名称空间的方式可以省略子标签property
,将组件属性的设置作为bean
标签的属性来完成。
<!-- 实验十 给bean的属性赋值:使用p名称空间 -->
<bean id="happyMachine3"
class="com.atguigu.ioc.component.HappyMachine"
p:machineName="goodMachine"
/>
使用p
名称空间需要导入相关的XML约束,在IDEA的协助下导入即可:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
测试
@Test
public void testExperiment10() {
HappyMachine happyMachine3 = (HappyMachine) iocContainer.getBean("happyMachine3");
String machineName = happyMachine3.getMachineName();
log.debug("machineName = " + machineName);
}
2.2.11 集合属性
配置
<!-- 实验十三 集合类型的bean -->
<bean id="happyTeam2" class="com.atguigu.ioc.component.HappyTeam">
<property name="memberList">
<list>
<value>member01</value>
<value>member02</value>
<value>member03</value>
</list>
</property>
</bean>
测试
@Test
public void testExperiment13() {
HappyTeam happyTeam2 = (HappyTeam) iocContainer.getBean("happyTeam2");
List<String> memberList = happyTeam2.getMemberList();
for (String member : memberList) {
log.debug("member = " + member);
}
}
其他变化形式
<!-- 实验十一 给bean的属性赋值:集合属性 -->
<bean id="happyTeam2" class="com.atguigu.ioc.component.HappyTeam">
<property name="memberNameList">
<!-- list标签:准备一组集合类型的数据,给集合属性赋值 -->
<!--<list>
<value>member01</value>
<value>member02</value>
<value>member03</value>
</list>-->
<!-- 使用set标签也能实现相同效果,只是附带了去重功能 -->
<!--<set>
<value>member01</value>
<value>member02</value>
<value>member02</value>
</set>-->
<!-- array也同样兼容 -->
<array>
<value>member01</value>
<value>member02</value>
<value>member02</value>
</array>
</property>
<property name="managerList">
<!-- 给Map类型的属性赋值 -->
<!--<map>
<entry key="财务部" value="张三"/>
<entry key="行政部" value="李四"/>
<entry key="销售部" value="王五"/>
</map>-->
<!-- 也可以使用props标签 -->
<props>
<prop key="财务部">张三2</prop>
<prop key="行政部">李四2</prop>
<prop key="销售部">王五2</prop>
</props>
</property>
</bean>
2.2.12 自动装配
声明组件类
其中HappyController
需要用到HappyService
。所谓自动装配就是一个组件需要其他组件时,由IOC容器负责找到那个需要的组件,并装配进去。
public class HappyController {
private HappyService happyService;
public HappyService getHappyService() {
return happyService;
}
public void setHappyService(HappyService happyService) {
this.happyService = happyService;
}
}
public class HappyService {
}
配置
<!-- 实验十二 自动装配 -->
<bean id="happyService3" class="com.atguigu.ioc.component.HappyService"/>
<bean id="happyService" class="com.atguigu.ioc.component.HappyService"/>
<!-- 使用bean标签的autowire属性设置自动装配效果 -->
<!-- byType表示根据类型进行装配,此时如果类型匹配的bean不止一个,那么会抛NoUniqueBeanDefinitionException -->
<!-- byName表示根据bean的id进行匹配。而bean的id是根据需要装配组件的属性的属性名来确定的 -->
<bean id="happyController"
class="com.atguigu.ioc.component.HappyController"
autowire="byName"
>
<!-- 手动装配:在property标签中使用ref属性明确指定要装配的bean -->
<!--<property name="happyService" ref="happyService"/>-->
</bean>
测试
@Test
public void testExperiment12() {
HappyController happyController = iocContainer.getBean(HappyController.class);
HappyService happyService = happyController.getHappyService();
log.debug("happyService = " + happyService);
}
2.2.13 集合类型的bean
配置
<!-- 实验十一 给bean的属性赋值:集合属性 -->
<util:list id="machineList">
<bean class="com.atguigu.ioc.component.HappyMachine">
<property name="machineName" value="machineOne"/>
</bean>
<bean class="com.atguigu.ioc.component.HappyMachine">
<property name="machineName" value="machineTwo"/>
</bean>
<bean class="com.atguigu.ioc.component.HappyMachine">
<property name="machineName" value="machineThree"/>
</bean>
</util:list>
测试
@Test
public void testExperiment11() {
List<HappyMachine> machineList = (List<HappyMachine>) iocContainer.getBean("machineList");
for (HappyMachine happyMachine : machineList) {
log.debug("happyMachine = " + happyMachine);
}
}
2.2.14 FactoryBean机制
简介
FactoryBean
是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个FactoryBean
类型的bean,在获取 bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()
方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。
将来我们整合Mybatis时,Spring 就是通过FactoryBean
机制来帮我们创建SqlSessionFactory
对象的。
实现FactoryBean
接口
// 实现FactoryBean接口时需要指定泛型
// 泛型类型就是当前工厂要生产的对象的类型
public class HappyFactoryBean implements FactoryBean<HappyMachine> {
private String machineName;
public String getMachineName() {
return machineName;
}
public void setMachineName(String machineName) {
this.machineName = machineName;
}
@Override
public HappyMachine getObject() throws Exception {
// 方法内部模拟创建、设置一个对象的复杂过程
HappyMachine happyMachine = new HappyMachine();
happyMachine.setMachineName(this.machineName);
return happyMachine;
}
@Override
public Class<?> getObjectType() {
// 返回要生产的对象的类型
return HappyMachine.class;
}
}
配置bean
<!-- 实验十四 FactoryBean机制 -->
<!-- 这个bean标签中class属性指定的是HappyFactoryBean,但是将来从这里获取的bean是HappyMachine对象 -->
<bean id="happyMachine3" class="com.atguigu.ioc.factory.HappyFactoryBean">
<!-- property标签仍然可以用来通过setXxx()方法给属性赋值 -->
<property name="machineName" value="iceCreamMachine"/>
</bean>
测试获取bean
- 配置的bean:
HappyFactoryBean
; - 获取bean后得到的bean:
HappyMachine
;
@Test
public void testExperiment14() {
HappyMachine happyMachine3 = (HappyMachine) iocContainer.getBean("happyMachine3");
String machineName = happyMachine3.getMachineName();
log.debug("machineName = " + machineName);
}
2.2.15 bean的作用域
概念
在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义参加下表:
取值 | 含义 | 创建对象的时机 | 默认值 |
---|---|---|---|
singleton | 在 IOC 容器中,这个 bean 的对象始终为单实例 | IOC 容器初始化时 | 是 |
prototype | 这个 bean 在 IOC 容器中有多个实例 | 获取 bean 时 | 否 |
说明:这里单实例、多实例指的是一个bean标签配置之后,对应一个对象还是多个对象。
如果是在WebApplicationContext
环境下还会有另外两个作用域(但不常用):
取值 | 含义 |
---|---|
request | 在一个请求范围内有效 |
session | 在一个会话范围内有效 |
配置
<!-- 实验十五 bean的作用域 -->
<!-- scope属性:取值singleton(默认值),bean在IOC容器中只有一个实例,IOC容器初始化时创建对象 -->
<!-- scope属性:取值prototype,bean在IOC容器中可以有多个实例,getBean()时创建对象 -->
<bean id="happyMachine4" scope="prototype" class="com.atguigu.ioc.component.HappyMachine">
<property name="machineName" value="iceCreamMachine"/>
</bean>
测试
@Test
public void testExperiment15() {
HappyMachine happyMachine01 = (HappyMachine) iocContainer.getBean("happyMachine4");
HappyMachine happyMachine02 = (HappyMachine) iocContainer.getBean("happyMachine4");
log.debug(happyMachine01 == happyMachine02);
log.debug("happyMachine01.hashCode() = " + happyMachine01.hashCode());
log.debug("happyMachine02.hashCode() = " + happyMachine02.hashCode());
}
2.2.16 bean的生命周期
生命周期清单
- bean 对象创建(调用无参构造器)
- 给 bean 对象设置属性(调用属性对应的 setter 方法)
- bean 对象初始化之前操作(由 bean 的后置处理器负责)
- bean 对象初始化(需在配置 bean 时指定初始化方法)
- bean 对象初始化之后操作(由 bean 的后置处理器负责)
- bean 对象就绪可以使用
- bean 对象销毁(需在配置 bean 时指定销毁方法)
- IOC 容器关闭
指定初始化方法和销毁方法
public void happyInitMethod() {
log.debug("HappyComponent初始化");
}
public void happyDestroyMethod() {
log.debug("HappyComponent销毁");
}
配置初始化和销毁方法
<!-- 实验十六 bean的生命周期 -->
<!-- 使用init-method属性指定初始化方法 -->
<!-- 使用destroy-method属性指定销毁方法 -->
<bean id="happyComponent"
class="com.atguigu.ioc.component.HappyComponent"
init-method="happyInitMethod"
destroy-method="happyDestroyMethod"
>
<property name="happyName" value="uuu"/>
</bean>
bean后置处理器
package com.atguigu.ioc.process;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
// 声明一个自定义的bean后置处理器
// 注意:bean后置处理器不是单独针对某一个bean生效,而是针对IOC容器中所有bean都会执行
public class MyHappyBeanProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("☆☆☆" + beanName + " = " + bean);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("★★★" + beanName + " = " + bean);
return bean;
}
}
后置处理器放入容器
<!-- bean的后置处理器要放入IOC容器才能生效 -->
<bean id="myHappyBeanProcessor" class="com.atguigu.ioc.process.MyHappyBeanProcessor"/>
执行效果
HappyComponent创建对象
HappyComponent要设置属性了
☆☆☆happyComponent = com.atguigu.ioc.component.HappyComponent@ca263c2
HappyComponent初始化
★★★happyComponent = com.atguigu.ioc.component.HappyComponent@ca263c2
HappyComponent销毁
2.3 基于注解管理bean
2.3.1 标记与扫描
@Component
注解标记的普通组件
package com.atguigu.ioc.component;
import org.springframework.stereotype.Component;
@Component
public class CommonComponent {
}
@Controller
注解标记的控制器组件
这个组件就是我们在三层架构中表述层里面,使用的控制器。以前是Servlet,以后我们将会使用Controller来代替Servlet。
package com.atguigu.ioc.component;
import org.springframework.stereotype.Controller;
@Controller
public class SoldierController {
}
@Service
注解标记的业务逻辑组件
package com.atguigu.ioc.component;
import org.springframework.stereotype.Service;
@Service
public class SoldierService {
}
@Repository
注解标记的持久化层组件
这个组件就是我们以前用的Dao类,但是以后我们整合了Mybatis,这里就变成了Mapper接口,而Mapper接口是由Mybatis和Spring的整合包负责扫描的。
由于Mybatis整合包想要把Mapper接口背后的代理类加入Spring的IOC容器需要结合Mybatis对Mapper配置文件的解析,所以这个事情是Mybatis和Spring的整合包来完成,将来由Mybatis负责扫描,也不使用@Repository注解。
package com.atguigu.ioc.component;
import org.springframework.stereotype.Repository;
@Repository
public class SoldierDao {
}
通过查看源码我们得知,@Controller
、@Service
、@Repository
这三个注解只是在@Component
注解的基础上起了三个新的名字。
对于Spring使用IOC容器管理这些组件来说没有区别,也就是语法层面没有区别。所以@Controller
、@Service
、@Repository
这三个注解只是给开发人员看的,让我们能够便于分辨组件的作用。
注意:虽然它们本质上一样,但是为了代码的可读性,为了程序结构严谨我们肯定不能随便胡乱标记。
扫描
- 最基本的扫描方式:
<!-- 配置自动扫描的包 -->
<!-- 最基本的扫描方式 -->
<context:component-scan base-package="com.atguigu.ioc.component"/>
- 指定匹配模式:
<!-- 情况二:在指定扫描包的基础上指定匹配模式 -->
<context:component-scan
base-package="com.atguigu.ioc.component"
resource-pattern="Soldier*.class"/>
- 指定要排除的组件:
<!-- 情况三:指定不扫描的组件 -->
<context:component-scan base-package="com.atguigu.ioc.component">
<!-- context:exclude-filter标签:指定排除规则 -->
<!-- type属性:指定根据什么来进行排除,annotation取值表示根据注解来排除 -->
<!-- expression属性:指定排除规则的表达式,对于注解来说指定全类名即可 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
- 仅扫描指定组件:
<!-- 情况四:仅扫描指定的组件 -->
<!-- 仅扫描 = 关闭默认规则 + 追加规则 -->
<!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
<context:component-scan base-package="com.atguigu.ioc.component" use-default-filters="false">
<!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
组件的bean ID
在我们使用XML方式管理bean的时候,每个bean都有一个唯一标识——id 属性的值,便于在其他地方引用。现在使用注解后,每个组件仍然应该有一个唯一标识。
默认情况:
简单类名首字母小写就是bean的id。例如:SoldierController
类对应的 bean 的 id 就是soldierController
。
使用value属性指定:
@Controller(value = "tianDog")
public class SoldierController {
}
当注解中只设置一个属性时,value属性的属性名可以省略:
@Service("smallDog")
public class SoldierService {
}
2.3.2 自动装配
自动装配的实现
前提:
参与自动装配的组件(需要装配别人、被别人装配)全部都必须在IOC容器中。
@Autowired
注解:
在成员变量上直接标记@Autowired
注解即可,不需要提供setXxx()
方法。以后我们在项目中的正式用法就是这样。
@Controller(value = "tianDog")
public class SoldierController {
@Autowired
private SoldierService soldierService;
public void getMessage() {
soldierService.getMessage();
}
}
@Service("smallDog")
public class SoldierService {
@Autowired
private SoldierDao soldierDao;
public void getMessage() {
soldierDao.getMessage();
}
}
标记在其他位置
构造器:
@Controller(value = "tianDog")
public class SoldierController {
private SoldierService soldierService;
@Autowired
public SoldierController(SoldierService soldierService) {
this.soldierService = soldierService;
}
……
setXxx()
方法:
@Controller(value = "tianDog")
public class SoldierController {
private SoldierService soldierService;
@Autowired
public void setSoldierService(SoldierService soldierService) {
this.soldierService = soldierService;
}
……
工作流程
@Controller(value = "tianDog")
public class SoldierController {
@Autowired
@Qualifier(value = "maomiService222")
// 根据面向接口编程思想,使用接口类型引入Service组件
private ISoldierService soldierService;
佛系装配
给@Autowired
注解设置required = false
属性表示:能装就装,装不上就不装。但是实际开发时,基本上所有需要装配组件的地方都是必须装配的,用不上这个属性。
@Controller(value = "tianDog")
public class SoldierController {
// 给@Autowired注解设置required = false属性表示:能装就装,装不上就不装
@Autowired(required = false)
private ISoldierService soldierService;
2.3.3 完全注解开发
使用配置类取代配置文件
创建配置类:
package com.atguigu.ioc.configuration;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConfiguration {
}
创建IOC容器对象:
// ClassPathXmlApplicationContext 根据 XM L配置文件创建 IOC 容器对象
private ApplicationContext iocContainer = new ClassPathXmlApplicationContext("applicationContext.xml");
// AnnotationConfigApplicationContext 根据配置类创建 IOC 容器对象
private ApplicationContext iocContainerAnnotation = new AnnotationConfigApplicationContext(MyConfiguration.class);
配置类中的配置bean
使用@Bean
注解
@Configuration
public class MyConfiguration {
// @Bean 注解相当于 XML 配置文件中的 bean 标签
// @Bean 注解标记的方法的返回值会被放入 IOC 容器
// 默认以方法名作为 bean 的 id
@Bean
public CommonComponent getComponent() {
CommonComponent commonComponent = new CommonComponent();
commonComponent.setComponentName("created by annotation config");
return commonComponent;
}
}
在配置类中自动扫描包
@Configuration
@ComponentScan("com.atguigu.ioc.component")
public class MyConfiguration {
……
2.3.4 整合junit4
整合的好处
- 好处1:不需要自己创建IOC容器对象了;
- 好处2:任何需要的bean都可以在测试类中直接享受自动装配;
操作
加入依赖:
<!-- Spring的测试包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.1</version>
<scope>test</scope>
</dependency>
创建测试类:
@Slf4j
// junit的@RunWith注解:指定Spring为Junit提供的运行器
@RunWith(SpringJUnit4ClassRunner.class)
// Spring的@ContextConfiguration指定Spring配置文件的位置
@ContextConfiguration(value = {"classpath:applicationContext.xml"})
public class JunitIntegrationSpring {
@Autowired
private SoldierController soldierController;
@Test
public void testIntegration() {
log.debug("soldierController = " + soldierController);
}
}