1、Spring简介
Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器框架。
Spring最初的出现是为了解决EJB(企业级Java Bean)臃肿的设计,以及难以测试等问题。
Spring为简化开发而生,让程序员只关注核心业务的实现,尽可能地不再关注非业务逻辑代码 (事务控制、安全日志等) 。
Spring是非侵入式的,Spring应用中的对象不依赖于Spring的特定类。
侵入式设计:在开发或者设计中,某一些类型或者API,依赖于别的API或者是别的容器,这样就属于侵入式设计。
- 侵入式设计不好做单元测试
- 例如 HttpServletRequest,如果某个方法依赖于 HttpServletRequest,而 HttpServletRequest 又存在于 tomcat 里面,需要人家tomcat的支撑,这样就不好做测试
也正是因为如此,Spring使我们能够编写出更干净、更可管理、并且更易于测试的代码
1.1 IOC
在传统的MVC架构模式中:表示层调用服务层,服务层再调用DAO层;
这样当我们实现业务的时候,只能new下一层的实现类对象来实现业务;
而且当我们有新的需求进来时,还必须重新编写原本可以运行的代码;
这样既违背了OCP原则,即开闭原则,又违背了DIP原则,即依赖倒置原则。
而 IOC(Inversion of Control),是一种编程思想,或者叫做一种新型的设计模式。将对象的创建权交出去,把对象和对象之间关系的维护权交出去,这个过程就叫做IOC。
上面提到:
Spring框架实现了IOC这种思想,它可以帮助我们:① new对象 ② 维护对象与对象之间的关系
Spring框架是一个实现了IOC思想的容器
那么,IOC的实现方式有很多种。其中比较重要的叫做依赖注入(Dependency Injection,简称DI)
让我们捋一下:
控制反转(IOC)是一种思想,依赖注入(DI)是这种思想的实现方式
而 DI 又包括两种常见的方式:① set注入(执行set方法给属性赋值) ② 构造方法注入(执行构造方法给属性赋值)
Dependency Injection:
- dependency是指A对象和B对象之间的关系
- Injection是指一种手段,通过这种手段可以让A对象和B对象产生关系
- dependency injection说的是对象A和B之间的关系,靠注入来维护(set注入,构造注入)
1.2 Spring八大模块
-
Spring Core(IOC模块)是最核心的模块,所有的模块都是建立在 IOC 的基础上,其次就是
-
Spring AOP(AOP),其他的模块都建立在 IOC + AOP 的基础上
-
Spring Web MVC,是Spring自己的一套MVC框架
-
Spring Webflux,是Spring提供的一款响应式的Web框架
-
Spring Web,可以通过它去集成其他第三方的MVC框架
-
Spring DAO,是Spring自己内置的JDBC操作
-
Spring ORM,用来集成ORM框架
-
Spring Context,提供扩展的服务
2、Spring程序
2.1 BeanFactory版
BeanFactory版(Spring中最核心的接口,但是主流还是ApplicationContext)
:
BeanFatcory是核心接口,项目运行过程中肯定有具体实现参与,这个具体实现就是
DefaultListableBeanFactory
,而ApplicationContext内部维护的Beanfactory也是它
① 新建maven项目,并导入对应依赖
<!-- Spring依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.7</version>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
② 在resources目录下新建一个Spring Config配置文件(导入Spring依赖才会有)
<?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直接对应工厂拿bean的bean name -->
<bean id="userService" class="com.yao.service.impl.UserServiceImpl">
<property name="userDao" ref="dao"/>
</bean>
<bean id="dao" class="com.yao.dao.impl.UserDaoImpl"></bean>
</beans>
③ 编写对应的Service层和Dao层,并在ServiceImpl里面注入Dao属性并且创造对应的set方法:
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
System.out.println("在xml文件中配置dao属性后,DI会通过set注入方法调用这个set");
this.userDao = userDao;
}
}
④ 单元测试
public class UserServiceTest {
/**
* 测试第一个Spring程序,BeanFactory版
*/
@Test
public void testMyFirstSpringApplication() {
// 创建工厂
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 创建一个读取器(xml文件)
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
// 读取配置文件给到工厂
reader.loadBeanDefinitions("beans.xml");
// 根据ID获取Bean实例对象
UserService userService = (UserService) beanFactory.getBean("userService");
UserDao userDao = (UserDao) beanFactory.getBean("dao");
// 如果不为空,说明已经帮我们创建好了这个bean
System.out.println(userService);
System.out.println(userDao);
}
}
上面使用BeanFactory完成了IOC的思想,对象的创造过程交给了Spring管理,以下是依赖注入的过程:
- 定义DAO接口及其对应的实现类
- 在Service实现类中注入DAO的属性并且添加Set方法
- 在Spring Bean的配置文件中通过
<bean>
标签中的<property>
属性进行注入- 再次拿去Service实现类的bean时,DAO属性已经通过Spring的DI注入,原理是里面的Set方法
2.2 ApplicationContext版
ApplicationContext版(主流,底层实际上还是对于BeanFactory的封装)
:
ApplicationContext称为Spring容器,内部封装了BeanFatocy,比BeanFactory的功能更强大(同时意味着启动后占用的资源更多)
只需要在单元测试里面换ApplicationContext的方式获取bean即可,其余的环境已经配好:
/**
* 测试第一个Spring程序,ApplicationContext版
*/
@Test
public void testApplicationContext() {
// ApplicationContext context = new FileSystemXmlApplicationContext("磁盘绝对路径");
// 默认去项目根路径下查找
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 获取对象
UserService userService = (UserService) context.getBean("userService");
// 如果不为空,说明已经帮我们创建好了这个bean
System.out.println(userService);
}
2.3 ApplicationContext与BeanFactory
BeanFactory与ApplicationContext的关系:
- BeanFactory称为“Spring的Bean工厂”,是Spring的早期接口,ApplicationContext是后期更高层的接口,称为“Spring容器”
ApplicationContext在BeanFactory的基础功能上进行了功能扩展
,如:监听功能、国际化功能等。BeanFactory的API更偏向底层
,ApplicationContext的API大多都是对BeanFactory中这些底层API进行封装- Bean创建的主要逻辑和功能都被封装在BeanFactory中(所以说它是Spring中的核心),
ApplicationContext不仅继承了BeanFactory,而且ApplicationContext内部还维护者对于BeanFactory的引用
。所以两者间既有继承又有融合Bean的初始化时机不同
。BeanFactory是在首次调用getBean时才进行Bean的创建,而ApplicationContext则是配置文件加载,容器一创建就将Bean都实例化并且初始化好
2.4 常用getBean的API
API | 返回值及参数 |
---|---|
Object getBean(String beanName) | 根据beanName从容器中获取Bean实例,要求容器中Bean唯一,返回值为Object,需要强转 |
T getBean(Class type) | 根据Class类型从容器中获取Bean实例,要求容器中Bean类型唯一,返回值为Class类型实例,无需强转 |
T getBean(String beanName, Class type) | 根据beanName从容器中获取Bean实例,返回值为Class类型实例,无需强转 |
3、基于XML的Spring应用
3.1 Bean的配置
首先是Bean
的配置:
bean标签中的属性 | 功能 |
---|---|
id 和class | Bean的id和全限定类名(如果不指定id,id就是他的全限定类名) |
name | 通过name设置Bean的别名,使得通过这个name也能直接获取Bean实例 |
scope | Bean的作用范围,BeanFactory作为容器时取值singleton和prototype |
lazy-init | Bean的初始化时机,是否延迟加载,BeanFactory作为容器时无效 |
init-method | Bean实例化后自动执行的初始化方法 |
destroy-method | Bean实例销毁前执行的方法 |
autowire | 设置自动注入模式,常用的有按照类型byType,按照名字byName |
factory-bean 和factory-method | 指定哪个工厂Bean的哪个方法完成Bean的创建 |
1、Bean的别名配置:
<!-- bean的id直接对应工厂拿bean时填入的bean的名字,name是别名,也可以通过它获取到该bean -->
<bean id="userService" name="aaa" class="com.yao.service.impl.UserServiceImpl"/>
2、Bean的作用范围:
- singleton:单例,默认值。Spring容器创建的时候,就会进行Bean的实例化,并存储到容器内部的单例池中,每次getBean时都是从单例池中获取相同的Bean实例
- prototype:原型。Spring容器初始化时不会创建Bean实例,当调用getBean时才会实例化Bean,每次getBean时都会创建一个新的Bean实例
<bean id="userService" name="aaa" class="com.yao.service.impl.UserServiceImpl" scope="singleton">
3、Bean的延迟加载:
当 lazy-init 设置为 true 时为延迟加载,也就是当Spring容器创建的时候,不会立即创建Bean实例,等待用到时再创建Bean实例并存储到单例池中去,后续继续使用该Bean直接去单例池中获取即可,本质上该Bean还是单例的
4、Bean的创建销毁:
实现类:
public class UserServiceImpl implements UserService, InitializingBean {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
System.out.println("在xml文件中配置后,DI会通过set注入方法调用这个set");
this.userDao = userDao;
}
public void init() {
System.out.println("init方法执行");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("在属性注入之后再执行该afterPropertiesSet方法");
}
public void destroy() {
System.out.println("destroy方法执行");
}
}
配置文件:
<bean id="userService" class="com.yao.service.impl.UserServiceImpl" init-method="init" destroy-method="destroy">
<property name="userDao" ref="dao"/>
</bean>
单元测试:
/**
* 测试Bean的生命周期
*/
@Test
public void testApplicationLife() {
// 默认去项目根路径下查找
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 获取对象
UserService userService = (UserService) context.getBean("userService");
// 关闭容器,如果指定了destroy方法则会先执行这个方法再关闭容器
context.close();
}
运行结果:
D:\Tools\jdk1.8\bin\java.exe ...
在xml文件中配置后,DI会通过set注入方法调用这个set
在属性注入之后再执行该afterPropertiesSet方法
init方法执行
destroy方法执行
3.2 Bean的构造方式
BeanFactory帮我们创造Bean时有两种方式:
- 通过反射获取到构造方法创造Bean
- BeanFactory中的工厂帮我们创造Bean,需要我们自己去指定
1、构造方法实例化bean(这里展示的是有参构造,前面在Bean的配置文件中配置的Bean就是无参)
// 假如有有参构造方法,可以在配置文件中配置
public UserServiceImpl(String name) {
System.out.println("有参构造方法被执行....");
}
配置文件:
<bean id="userService" class="com.yao.service.impl.UserServiceImpl">
<constructor-arg name="name" value="aabbc"></constructor-arg>
<property name="userDao" ref="dao"/>
</bean>
但是
<constructor-arg>
并不仅仅只可以用来有参构造方法传参,只要在创建Bean时需要的参数,都可以通过这个标签传入
2、工厂实例化bean
工厂实例化bean需要指定,又有3种方式:
- 静态工厂方法实例化bean(不需要实例对象)
- 实例工厂方法实例化bean(需要实例对象)
- 实现FactoryBean规范延迟实例化Bean
① 静态工厂
// 自定义的静态工厂实例化bean
public class MyStaticBeanFactory {
private static UserDao userDao() {
// Bean在创建前可以进行一些其他业务的操作
return new UserDaoImpl();
}
}
<!--
有了factory-method的配置后,就会告诉Spring,这个bean的配置的目的不是为了创造这个bean,
而是想要获得factory-method中返回的对象。采用这种方式实例化bean可以使业务代码更加灵活,
因为可以使bean创造之前进行一些额外的业务操作;也可以直接引入某些java包中的静态方法
比如DrivenManager.getConnection
-->
<bean id="staticFactory" class="com.yao.factory.MyStaticBeanFactory" factory-method="userDao"/>
② 实例工厂
// 自定义实例化工厂,获取bean的方法没有static
public class MyBeanFactory {
private UserDao userDao() {
// Bean在创建前可以进行一些其他业务的操作
return new UserDaoImpl();
}
}
<!-- 用实例化工厂创造 Bean的实例 -->
<bean id="factory" class="com.yao.factory.MyBeanFactory"></bean>
<!--
getBean("userDao") -> factory-bean指定的工厂对象 -> factory-method指定的方法 -> 方法中的返回值
采用这种实例化工厂的方式创造bean实例,同样能够使业务代码更加灵活;在某些第三方jar包中的某些bean的
产生就是通过某些对象的方法去产生的,就可以用来这样配置那些bean
-->
<bean id="userDao" factory-bean="factory" factory-method="userDao"></bean>
③ FactoryBean规范延迟实例化
用这种方法实例化bean:当接到实例化的请求时,FactoryBean不予理睬,
只有当真正用到该bean时
,再去创建;并且把创建过后的bean存到factoryBeanObjectCache里面,下次再用到该bean时直接从缓存中去取;如果没有,再去创建,以此往复
public class BeanFactory implements FactoryBean<UserDao> {
@Override
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
@Override
public Class<?> getObjectType() {
return UserDao.class;
}
}
<!-- 配置该bean后,获取bean时得到的是getObject中返回的对象 -->
<bean id="userDao2" class="com.yao.factory.BeanFactory"></bean>
Bean实例化的基本流程:
- Spring容器在进行初始化时,会将xml配置的
<bean>
的信息封装成一个BeanDefinition
对象,所有的BeanDefinition
对象存储到一个名为beanDefinitionMap
的Map集合中去,这个Map集合维护在本地的BeanFactory中- Spring可以在
beanDefinitionMap
这个Map集合中遍历取出每一个BeanDefinition
的信息,然后通过反射来创建Bean的实例对象- 创建好的Bean对象就存储在一个名为
singletonObjects
的Map集合中,当调用getBean方法时则最终从该Map集合中取出Bean实例对象并返回,这个Map集合也还是维护在本地的BeanFactory中- 当调用getBean(“名字”)时,会根据这个名字去
singletonObjects
Map集合中去匹配,成功就拿到,不成功就报错
3.3 Bean的依赖注入配置
注入方式 | 配置方式 |
---|---|
通过Bean的set方法注入 (注入的bean在配置文件中有配置) | <property name="userDao" ref="userDao" /> |
通过Bean的set方法注入 (注入的是普通属性) | <property name="user" value="aabbc" /> |
通过构造Bean的方法注入 (注入的参数bean在配置文件中有配置) | <constructor-arg name="userDao" ref="userDao" /> |
通过构造Bean的方法注入 (注入的参数是普通属性) | <constructor-arg name="user" ref="aabbc" /> |
以上两种:set注入、构造方法注入,前面都有提及。现在介绍集合数据类型的注入
① List集合:
public class UserServiceImplOfTestListInject implements UserService {
private List<String> list = new ArrayList<>();
public void setList(List<String> list) {
this.list = list;
}
private List<UserDao> daoList = new ArrayList<>();
public void setDaoList(List<UserDao> daoList) {
this.daoList = daoList;
}
// 打印
public void show() {
System.out.println(list);
System.out.println(daoList);
}
}
<?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="userService" class="com.yao.service.impl.UserServiceImplOfTestListInject">
<property name="list">
<list>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</list>
</property>
<property name="daoList">
<list>
<!-- 也可以通过外部bean配置,然后这里ref引入 -->
<bean id="userDao1" class="com.yao.dao.impl.UserDaoImpl"></bean>
<bean id="userDao2" class="com.yao.dao.impl.UserDaoImpl"></bean>
<bean id="userDao3" class="com.yao.dao.impl.UserDaoImpl"></bean>
</list>
</property>
</bean>
</beans>
/**
* 测试集合注入
*/
@Test
public void testListInject() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("listBeans.xml");
// 获取对象
UserServiceImplOfTestListInject impl = (UserServiceImplOfTestListInject) context.getBean("userService");
// 打印
impl.show();
}
② Set集合仅仅只是将<List>
换成<Set>
即可
③ Map集合:
<property name="map">
<map>
<!-- 如果是普通类型,就是value="xxx" -->
<entry key="d1" value-ref="userDao1">
<entry key="d2" value-ref="userDao2">
</map>
</property>
3.4 自动装配
如果被注入的属性是Bean引用的话,可以在
<bean>
标签中使用autowire
属性去配置自动注入的方式:
- byName:通过属性名自动装配,即去匹配
setXxx
与id="xxx"
或者name="xxx"
是否一致- byType:通过Bean的类型从容器中匹配,匹配出多个相同Bean类型时会报错
<bean id="userService" class="com.yao.service.impl.UserServiceImpl" autowire="byType">
<!--自动装配进去的bean需要配置!!!-->
<bean id="userDao" class="com.yao.dao.impl.UserDaoImpl">
注意:自动装配的前提是该自动装配所需要的bean已在容器中配置
3.5 其他标签
Spring的xml标签大体上分为两类:
- 默认标签:就是不用额外导入其他命名空间约束的标签,如
<bean>
- 自定义标签:就是需要额外引入其他命名空间约束,并通过前缀引用的标签,例如
<context:property-placeholder/>
1、引入自定义标签:①导入坐标依赖 ②引入命名空间 ③引入schema路径 ④使用标签
<?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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 使用上面配置的自定义标签 -->
<context:property-placeholder></context:property-placeholder>
<!--
xmlns:xml namespace
schemaLocation: schema文件的地址(看似是网址,实际上是映射到本地的某些jar包中的schema)
-->
</beans>
2、默认标签:
beans
<beans profile="test">
<bean id="userService" class="com.yao.service.impl.UserServiceImpl"></bean>
<bean id="dao" class="com.yao.dao.impl.UserDaoImpl"></bean>
</beans>
<beans profile="dev">
<bean id="dao" class="com.yao.dao.impl.UserDaoImpl"></bean>
</beans>
/**
* 测试beans的切换
*/
@Test
public void testBeans() {
System.setProperty("spring.profiles.active", "dev");
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("otherTag.xml");
// 获取对象
UserDao dao = (UserDao) context.getBean("dao");
System.out.println(dao);
context.close();
}
import
<!-- 通过import引入其他模块的配置文件 -->
<import resource="classpath:applicationContext-user.xml"></import>
<import resource="classpath:applicationContext-orders.xml"></import>
alias
<!-- userDao是在容器中配置的bean的id -->
<alias alias="aaa" name="userDao"></alias>
3.6 配置非自定义的Bean
到目前为止,前面介绍的bean的配置都属于自定义的bean的配置。但是在实际开发中,有些功能类并不是我们自己定义的,而是使用第三方jar包中的。那么,这些bean想要让Spring进行管理,也需要对其进行配置。
配置非自定义的Bean需要考虑如下两个问题(看第三方jar包中的方式来决定):
- 被配置的Bean的实例化方式是什么?无参构造、有参构造、静态工厂还是实例工厂
- 被配置的Bean是否需要注入必要属性
比如:
① 导入坐标依赖
<!--druid数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
② bean的配置
<!--配置数据源信息-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test?ServerTime=UTC"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--配置Connection-->
<bean id="connection" class="java.sql.DriverManager" factory-method="getConnection" scope="prototype">
<constructor-arg name="url" value="jdbc:mysql://localhost:3306/test?ServerTime=UTC"></constructor-arg>
<constructor-arg name="user" value="root"></constructor-arg>
<constructor-arg name="password" value="root"></constructor-arg>
</bean>
<!--配置日期对象-->
<bean id="simpleDateFormat" class="java.text.SimpleDateFormat">
<constructor-arg name="pattern" value="yyyy-MM-dd HH:mm:ss"></constructor-arg>
</bean>
<bean id="date" factory-bean="simpleDateFormat" factory-method="parse">
<constructor-arg name="source" value="2023-7-21 12:00:00"></constructor-arg>
</bean>
4、Spring的后处理器
Spring的后处理器是Spring开发的重要扩展点,它允许我们介入到Bean的整个实例化流程中来。从而能够实现动态注册BeanDefinition, 动态修改BeanDefinition,以及动态修改Bean等功能需求。Spring主要有两种后处理器:
BeanFactoryPostProcessor
:Bean工厂后处理器,在BeanDefinitionMap填充完毕,Bean实例化之前执行(执行1次)BeanPostProcessor
:Bean后处理器,一般在Bean实例化之后,填充到单例池singletonObjects之前执行(每个bean创建完成后都会执行1次)
4.1 BeanFactoryPostProcessor
一个接口规范,只要交给Spring容器管理,那么Spring就会回调该接口的方法,用于对BeanDefinition注册和修改的功能
1、修改
:
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("beanDefinitionMap填充完毕后执行");
// 在这里做点手脚,修改某个BeanDefinition
BeanDefinition beanDefinition = beanFactory.getBeanDefinition("userService");
beanDefinition.setBeanClassName("com.yao.dao.impl.UserDaoImpl");
}
<bean id="userService" class="com.yao.service.impl.CommonUserServiceImpl"></bean>
<!--配置Spring后处理器-->
<bean class="com.yao.process.MyBeanFactoryPostProcessor"></bean>
当获取userService
bean的时候,会返回UserDaoImpl
的实例
2、注册
:
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("beanDefinitionMap填充完毕后执行");
// // 修改某个BeanDefinition
// BeanDefinition beanDefinition = beanFactory.getBeanDefinition("userService");
// beanDefinition.setBeanClassName("com.yao.dao.impl.UserDaoImpl");
// 动态注册BeanDefinition进入BeanDefinitionMap集合中
BeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClassName("com.yao.dao.impl.OrderDaoImpl");
// 这里, ConfigurableListableBeanFactory没有注册方法, 但是DefaultListableBeanFactory有, 所以这里先强转
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory;
defaultListableBeanFactory.registerBeanDefinition("orderDao", beanDefinition);
}
}
在bean的配置文件中不用配置bean,而是通过这种方式动态注册
/**
* 测试Spring后处理器-BeanFactoryPostProcessor-注册
*/
@Test
public void testPostProcess_reg() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("commonBeans.xml");
Object impl = context.getBean("orderDao");
System.out.println(impl);
}
beanDefinitionMap填充完毕后执行
com.yao.dao.impl.OrderDaoImpl@28ac3dc3
Spring还提供了一个BeanFactoryPostProcessor
的子接口BeanDefinitionRegistryPostProcessor
专门用于注册BeanDefinition操作,如果之后我们只是想要去动态注册BeanDefinition,可以直接实现BeanDefinitionRegistryPostProcessor
:
public class MyBeanDefinitionRegisterProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
System.out.println("MyBeanDefinitionRegisterProcessor的postProcessBeanDefinitionRegistry被执行!");
BeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClassName("com.yao.dao.impl.OrderDaoImpl");
beanDefinitionRegistry.registerBeanDefinition("orderDao", beanDefinition);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
System.out.println("MyBeanDefinitionRegisterProcessor的postProcessBeanFactory被执行!!");
}
}
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("beanDefinitionMap填充完毕后执行!!!");
}
}
/**
* 测试Spring后处理器-BeanFactoryPostProcessor-注册
*/
@Test
public void testPostProcess_reg() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("commonBeans.xml");
Object impl = context.getBean("orderDao");
System.out.println(impl);
}
}
<?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="userService" class="com.yao.service.impl.CommonUserServiceImpl"></bean>
<!--配置BeanFactoryPostProcessor-->
<bean class="com.yao.process.MyBeanFactoryPostProcessor"></bean>
<!--配置BeanDefinitionRegistryPostProcessor-->
<bean class="com.yao.process.MyBeanDefinitionRegisterProcessor"></bean>
</beans>
MyBeanDefinitionRegisterProcessor的postProcessBeanDefinitionRegistry被执行!
MyBeanDefinitionRegisterProcessor的postProcessBeanFactory被执行!!
beanDefinitionMap填充完毕后执行!!!
com.yao.dao.impl.OrderDaoImpl@1d371b2d
通过上面的演示,我们还可以知道各方法之间的执行顺序
4.2 BeanPostProcessor
Bean被实例化后,到最终缓存到名为singletonObjects单例池之前,中间会经过Bean的初始化过程。例如属性的填充、初始化方法init的执行等。其中有一个对外进行扩展的点
BeanPostProcessor
,我们称之为Bean后处理器
。跟工厂后处理器相似,它也是一个接口,实现了该接口并被容器管理的BeanPostProcessor,会在流程节点上被Spring调用
public class OrderDaoImpl implements OrderDao {
private String orderName;
public void setOrderName(String orderName) {
this.orderName = orderName;
}
public String getOrderName() {
return orderName;
}
public OrderDaoImpl() {
System.out.println("OrderDaoImpl实例化");
}
}
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println(beanName + ":postProcessBeforeInitialization");
if(bean instanceof OrderDaoImpl) {
OrderDaoImpl impl = (OrderDaoImpl) bean;
impl.setOrderName("密西西比口味烧饼!");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println(beanName + ":postProcessAfterInitialization");
return bean;
}
}
<bean id="orderDao" class="com.yao.dao.impl.OrderDaoImpl"></bean>
<bean class="com.yao.process.MyBeanPostProcessor"></bean>
/**
* 测试Spring后处理器-BeanPostProcessor
*/
@Test
public void testPostProcess_bean() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("commonBeans.xml");
OrderDaoImpl impl = (OrderDaoImpl) context.getBean("orderDao");
System.out.println(impl + " 订单名字是:" + impl.getOrderName());
}
}
OrderDaoImpl实例化
orderDao:postProcessBeforeInitialization
orderDao:postProcessAfterInitialization
com.yao.dao.impl.OrderDaoImpl@8e24743 订单名字是:密西西比口味烧饼!
经过上面的演示,我们知道了Bean后处理器可以让我们在Bean存入Map集合之前对Bean做一些处理
5、 Spring Bean的生命周期
Spring Bean的生命周期是从Bean实例化后(即通过反射创建出对象之后),到Bean成为一个完整对象,最终存储到单例池中,这整个过程被称为Spring Bean的生命周期,Spring Bean的生命周期大致可分为3个阶段:
Bean的实例化阶段
:Spring框架会取出BeanDefinition的信息进行判断当前Bean的作用范围是否是singleton,是否不是延迟加载的,是否不是FactoryBean等,最终将一个普通的singleton的Bean通过反射进行实例化Bean的初始化阶段
:Bean创建之后还仅仅是个“半成品”,还需要对Bean实例的属性进行填充、执行一些Aware接口方法、执行BeanPostProcessor方法、执行InitializingBean接口的初始化方法、执行自定义初始化init方法等。该阶段是Spring最具技术含量和复杂度的阶段:AOP增强功能,Spring的注解功能等、Bean的循环引用问题都是在这个阶段体现的Bean的完成阶段
:经过初始化阶段,Bean就成为了一个完整的Spring Bean,被存储到单例池singletonObjects中去了,即完成了Spring Bean的整个生命周期
这里重点是介绍Bean的初始化阶段
:
Bean的初始化过程涉及如下几个过程:
第1步 => Bean实例的属性填充
第2步 => Aware接口属性注入
第3步 => BeanPostProcessor的before()方法回调
第4步 => InitializingBean接口的初始化方法回调
第5步 => 自定义初始化方法init回调
第6步 => BeanPostProcessor的after()方法回调
5.1 Bean实例的属性填充
BeanDefinition中有对当前Bean实体的注入信息通过属性propertyValues进行存储,Spring在进行属性注入时,会分为以下几种情况:
注入普通属性
。String、int或存储基本类型的集合时,直接通过set方法的反射 注入注入单向引用属性的bean对象
。从容器中getBean获取后通过set方法反射 注入;如果容器中没有,则先创建被注入对象Bean实例(完成整个生命周期)后,在进行注入操作注入双向引用属性的bean对象
。这种情况就比较复杂了,涉及到了循环引用
/循环依赖
的问题
5.2 循环依赖
多个实体之间相互依赖并形成闭环的情况就叫做
循环依赖
/循环引用
如这样一种情况:
=> 在xml中配置了userService和userDao的Bean,两者内部的属性都是对方,即循环依赖;
=> 当getBean("userService")
时会先去实例化Service,然后初始化Service去填充userDao属性,但是发现容器中没有,则会去创建userDao;
=> 创建Dao又会去实例化,然后初始化填充userService的属性,发现容器中没有 (因为userService还不是完整bean,在单例池中还不存在) ,又会去创建userService,然后又会进入一个循环。
如何解决这个问题?只要当我们实例化一个bean,就把这个还未完整的bean存到另一个集合当中。当我们需要找bean时,先去单例池中找,单例池中没有,再去这个集合中找
Spring提供了三级缓存
来解决循环依赖问题
public class DefaultSingletonBeanRegistry ... {
// 1、最终存储单例 Bean成品的容器,即实例化和初始化都完成的 Bean,称之为 “一级缓存”
Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
// 2、早期 Bean单例池,缓存半成品对象,且当前对象已经被其他对象引用了,称之为 “二级缓存”
Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
// 3、单例 Bean的工厂池,缓存半成品对象,对象未被引用,使用时在通过工厂创建 Bean,称之为 “三级缓存”
Map<String, objectFactory<?>> singletonFactories = new HashMap(16);
}
那么现在的流程:
=> 在xml中配置了userService和userDao的Bean,两者内部的属性都是对方,即循环依赖;
=> 当getBean("userService")
时会先去实例化userService,并将该未完整的bean放入三级缓存中,并用objectFactory
对其进行封装【只要实例化后的bean,都会被包装成objectFactory
,然后存入到三级缓存中】,然后初始化后的userService需要填充userDao属性,但是发现单例池(一级缓存)
中没有中没有;则会去早期Bean单例池(二级缓存)
中找,这也没有userDao;又去单例Bean工厂池(三级缓存)
中找,也没有;
=> 最后决定去创建userDao的bean,实例化后,在初始化中为userDao注入userService属性,重复上述的查找顺序,最后在三级缓存中找到了userService,由于内部维护着对于该userService的引用,所以userService进入了二级缓存。最后完成userDao的初始化,将userDao放入单例池中 (一级缓存),并且删除二三级缓存;
=> 既然有了userDao,那么userService也会初始化完毕,结束后将userService放入单例池中 (一级缓存),并且删除二三级缓存,整个过程结束。
5.3 Aware接口
Aware接口是一种框架辅助属性注入的一种思想,其他框架中也可以看到类似的接口。框架具备高度封装性,我们接触到的一般只有业务代码,一个底层功能的API不能轻易的获取到,但这不意味着永远用不到这些API,如果用到了,就可以使用框架提供的类似这里的Aware接口来让框架给我们注入对象,只需要在实体类中实现这些接口的回调方法即可获得注入属性
常用Aware接口
:
Aware接口 | 回调方法 | 作用 |
---|---|---|
ServletContextAware | setServletContext(ServletContext context) | Spring框架回调方法注入ServletContext对象,web环境下才生效 |
BeanFactoryAware | setBeanFactory(BeanFactory factory) | Spring框架回调方法注入beanFactory对象 |
BeanNameAware | setBeanName(String beanName) | Spring框架回调方法注入当前Bean在容器中的beanName |
ApplicationContextAware | setApplicationContext(ApplicationContext applicationContext) | Spring框架回调方法注入applicationContext对象 |