Spring IoC 知识图
一、概述
1. IoC 与 DI
IoC: 控制反转。把对象创建(实例化、管理)的控制权交给外部环境(spring 框架、IoC 容器)
DI: 依赖注入。容器把被依赖对象注入到依赖对象中
2. BeanFactory 与 ApplicationContext 区别
2.1. BeanFactory 基础容器
- 顶层容器(根容器),不能被实例化。
- 加载配置文件,解析成 BeanDefinition 放在 Map 里。
- 调用 getBean 的时候,从 BeanDefinition 所属的 Map 里,拿出 Class 对象进行实例化,同时,如果有依赖关系,将递归调用 getBean 方法 —— 完成依赖注入。
2.2. ApplicationContext 高级容器
- 包含了基础容器的功能,当他执行 refresh 模板方法的时候,将刷新整个容器的 Bean。
- 支持不同信息源头,支持 BeanFactory 工具类,支持层级容器,支持访问文件资源,支持事件发布通知,支持接口回调等
- 即时加载、自己创建和管理资源对象、支持国际化、支持基于依赖的注解
二、XML 方式
1. 实例化 Bean 方式
1.1. 使用无参构造函数
1 | <!-- 配置 service 对象 --> |
1.2. 使用静态方法创建
class 属性: 指定静态方法所在的类
factory-method: 指定静态方法名
1 | <!-- 使⽤静态⽅法创建对象的配置⽅式 --> |
1.3. 使用实例化方法创建
factory-bean: 指定实例化方法所在的 bean
factory-method: 指定 factory-bean 中的实例化方法
1 | <!-- 使⽤实例⽅法创建对象的配置⽅式 --> |
2. 属性装配
2.1. 构造函数注入
构造函数:
1 | public class JdbcAccountDaoImpl { |
对应 bean 配置:
1 | <bean id="userService" class="com.lagou.edu.dao.impl.JdbcAccountDaoImpl"> |
2.2. set 方法注入
property 对应类中的字段属性
1 | <bean id="userService" class="com.lagou.edu.dao.impl.JdbcAccountDaoImpl"> |
三、注解方式
1. 实例化 Bean 方式
- _@Configuration:_表明该类为配置类
- _@ComponentScan:_这个注解能够在 Spring 中启用组件扫描。 默认会扫描与配置类相同的包
- _@PropertySource:_引⼊外部属性配置⽂件
- _@Import:_引⼊其他配置类
- _@ImportResource:_引用 XML 配置
- _@Value:_对变量赋值,可以直接赋值,也可以使⽤ ${} 读取资源配置⽂件中的信息
1
2
3
4
5
6
public class SoundSystemConfig {
}
1.1. 组件实现实例化
通过 @ComponentScan 类路径扫描来自动侦测以及自动装配到 Spring 容器中
用于自定义类
- @Component:基本注解,标识了一个受 Spring 管理的组件
- @Respository:标识持久层组件
- @Service:标识服务层(业务层)组件
- @Controller:标识表现层组件
1.2. @Bean 实现实例化
在 @Configuration 定义的配置类中使用可注册并获得 bean
通常同于第三方插件实例化
1 |
|
2. 属性装配
@Autowired: 按照类型注⼊
@Qualifier: 告诉 Spring 具体去装配哪个对象
1
2
3
4
5
6
public class TransferServiceImpl {
private AccountDao accountDao;
}@Resource: 注解由 J2EE 提供,默认按照 ByName ⾃动注⼊
@Primary: 可以通过 @Primary 来表达最喜欢的方案
1
2
3
4
5
6
7
8
9
10
11
12
public class IceCream implements Dessert {
...
}
// Bean 方法
public Dessert iceCream() {
return new IceCream();
}
四、启动容器方式
1. Java 环境下启动 IoC 容器
- ClassPathXmlApplicationContext:从类的根路径下加载配置⽂件(推荐使⽤)
- FileSystemXmlApplicationContext:从磁盘路径上加载配置⽂件
- AnnotationConfigApplicationContext:纯注解模式下启动 Spring 容器
2. Web 环境下启动 IoC 容器
从 xml 启动容器:
1 |
|
从配置类启动容器:
1 |
|
五、bean 的作用域
1. 类型
- 单例(Singleton): 在整个应用中,只创建 bean 的一个实例
- _原型(Prototype)_: 每次注入或者通过 Spring 应用上下文获取的时候,都会创建一个新的 bean 实例
- 会话(Session): 在 Web 应用中,为每个会话创建一个 bean 实例
- 请求(Request): 在 Web 应用中,为每个请求创建一个 bean 实例
2. 实现
xml:
1 | <bean id="userService" class="com.lagou.service.impl.TransferServiceImpl" scope="prototype"> </bean> |
注解:
1 |
|
六、高级特性
1. lazy-Init
_懒加载:_不会在容器初始化时加载这个 bean ,⽽是第⼀次向容器通过 getBean 索取 bean 时实例化
xml:<bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="true" />
注解:@Lazy
2. FactoryBean
(63 条消息) Spring - FactoryBean 的使用场景和源码_阿里巴巴首席技术官的博客 - CSDN 博客_factorybean 使用场景
可以⽣成某⼀个类型的 Bean 实例,可以借助于它⾃定义 Bean 的创建过程
CompanyFactoryBean:
1 | // 可以让我们⾃定义 Bean 的创建过程(完成复杂 Bean 的定义) |
bean 定义:
当获取 id 为 companyBean 的 bean 时会返回 Company 类型的 bean
获取 FactoryBean,需要在 id 之前添加 “&”,如:”&companyBean”
1 | <bean id="companyBean" class="com.lagou.edu.factory.CompanyFactoryBean"> |
3. profile bean
1 |
|
- spring.profiles.active: 如果设置了
spring.profiles.active
属性的话,那么它的值就会用来确定哪个 profile 是激活的 - spring.profiles.default: 如果没有设置
spring.profiles.active
属性的话,那 Spring 将会查spring.profiles.default
的值
设置属性方式:
- 作为 DispatcherServlet 的初始化参数
- 作为 Web 应用的上下文参数
- 作为 JNDI 条目
- 作为环境变量
- 作为 JVM 的系统属性
- 在集成测试类上,使用 @ActiveProfiles 注解
4. 条件化配置 Bean
1 |
|
通过 ConditionContext,我们可以做到如下几点:
- 借助 getRegistry () 返回的 BeanDefinitionRegistry 检查 bean 定义
- 借助 getBeanFactory () 返回的 ConfigurableListableBeanFactory 检查 bean 是否存在,甚至探查 bean 的属性
- 借助 getEnvironment () 返回的 Environment 检查环境变量是否存在以及它的值是什么
- 读取并探查 getResourceLoader () 返回的 ResourceLoader 所加载的资源
- 借助 getClassLoader () 返回的 ClassLoader 加载并检查类是否存在
5. 使用 Enviroment 注入
1 | Configuration |
七、Bean 生命周期
容器启动后会创建 BeanFactory,并加载配置的 bean 信息成 BeanDefinition 放入 Map
这之后会执行自定义的 Bean 工厂后置处理器(实现 BeanFactoryPostProcessor 接口)中的 postProcessBeanFactory 方法。可从中获得 bean 配置信息(BeanDefinition)1
2
3
4
5
6
7
8
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) {
...
}
}这之后会对 scope 为 singleton 且非懒加载的 bean 进行实例化
按照 Bean 定义信息配置信息,注入所有的属性
如果 Bean 实现了 BeanNameAware 接口,会回调该接口的 setBeanName () 方法,传入该 Bean 的 id。此时该 Bean 就获得了自己在配置文件中的 id
如果 Bean 实现了 BeanFactoryAware 接口,会回调该接口的 setBeanFactory () 方法,传入该 Bean 的 BeanFactory。这样该 Bean 就获得了自己所在的 BeanFactory
如果 Bean 实现了 ApplicationContextAware 接口,会回调该接口的 setApplicationContext () 方法,传入该 Bean 的 ApplicationContext。这样该 Bean 就获得了自己所在的 ApplicationContext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Result implements BeanNameAware, BeanFactoryAware, ApplicationContextAware {
public void setBeanName(String name) {
System.out.println("该对象注册成 bean 时定义的 id:" + name);
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("该对象的 beanFactory:" + beanFactory);
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("applicationContext:" + applicationContext);
}
}如果有 Bean 实现了 BeanPostProcessor 接口,会回调该接口的 postProcessBeforeInitialzation () 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyBeanPostProcessor implements BeanPostProcessor {
/**
* init-method 方法执行之前被调用
*/
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if ("lazyResult".equalsIgnoreCase(beanName)) {
System.out.println("MyBeanPostProcessor before 方法拦截处理 lazyResult");
}
return bean;
}
}如果 Bean 实现了 InitializingBean 接口,则会回调该接口的 afterPropertiesSet () 方法,@PostConstruct 注解的方法在该方法之前被调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Result implements InitializingBean {
// 在 afterPropertiesSet 之前执行
public void postConstruct() {
System.out.println("postConstruct");
}
public void afterPropertiesSet() throws Exception {
System.out.println("afterPropertiesSet");
}
}如果 Bean 配置了 init-method 方法,则会执行 init-method 配置的方法,@PostConstruct 注解的方法在该方法之前被调用
1
2
3
4
5
6
7
8
9
10
public class Result {
// 在 afterPropertiesSet 之后执行
public void initMethod() {
System.out.println("initMethod");
}
}
<bean id="lazyResult" class="com.xxx.beans.Result" init-method="initMethod"></bean>如果有 Bean 实现了 BeanPostProcessor 接口,则会回调该接口的 postProcessAfterInitialization () 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyBeanPostProcessor implements BeanPostProcessor {
/**
* initmethod 方法执行之后被调用
*/
public Object postProcessAfterInitialization(Object beanm String beanName) {
if ("lazyResult".equalsIgnoreCase(beanName)) {
System.out.println("MyBeanPostProcessor after 方法拦截处理 lazyResult");
}
return bean;
}
}使用该 Bean
- scope 为 singleton 的 Bean:Spring 的 IoC 容器中会缓存一份该 bean 的实例(singletonObjects 中)
- scope 为 prototype 的 Bean:每次使用都会 new 一个新的对象,对象的生命周期不归 IoC 容器管理,对象的销毁交给垃圾回收器
容器关闭后,如果 Bean 实现了 DisposableBean 接口,则会回调该接口的 destroy () 方法,@PreDestory 注解的方法在该方法之前被调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Result implements DisposableBean {
// 在 destroy 之前执行
public void preDestroy() {
System.out.println("preDestroy");
}
public void destroy() throws Exception {
System.out.println("destroy");
}
}如果 Bean 配置了 destroy-method 方法,则会执行 destroy-method 配置的方法
1
2
3
4
5
6
7
8
public class Result {
// 在 destroy 之后执行
public void destroyMethod() {
System.out.println("destroyMethod");
}
}
八、源码分析
Spring IoC 的容器体系:
1. 容器初始化分析
1 | // 容器初始化 |
最终调用 AbstractApplicationContext#refresh 方法进行容器初始化
refresh 主要流程:
- 初始化工厂
- obtainFreshBeanFactory 方法: 获取 DefalutListableBeanFactory 工厂、解析 xml 加载 BeanDefinition 并注册到 BeanDefinitionRegistry
- 调用 refreshBeanFactory 方法:关闭以前的工厂,初始化一个新工厂(DefaultListableBeanFactory)并自定义属性
- 调用
**loadBeanDefinitions**
方法:最终调用 XmlBeanDefinitionReader#loadBeanDefinitions 加载 BeanDefinition
- invokeBeanFactoryPostProcessors 方法: 生成实现了 BeanFactoryPostProcessor 的 bean,并调用其方法
- 初始化 Bean
- registerBeanPostProcessors 方法: 注册 BeanPostProcessor 后置处理器(目前阶段尚未执行)
- finishBeanFactoryInitialization 方法:
调用 ConfigurableListableBeanFactory#preInstantiateSingletons 来实例化所有非懒加载的单例 bean
遍历所有 beanName,若是非抽象、单例、非懒加载的 beanDefinition 则调用**AbstractBeanFactory#getBean**
下面重点介绍 loadBeanDefinitions
和 getBean
方法
1.1. loadBeanDefinitions
XmlBeanDefinitionReader#loadBeanDefinitions
作用: 加载 bean 配置信息
重要步骤:
- doLoadDocument:获取 XML DOM 文档
- registerBeanDefinitions:从 DOM 文档中读取 beanDefinition 并将它们注册到 readerContext 里的注册表中
- createDelegate:创建委托类进行初始化
- parseBeanDefinitions:解析标签注册 beanDefinition
1.2. getBean
获取 bean 方法,从中可以看出 bean 的生命周期
重要步骤:
- getSingleton:分别从一二三级缓存中获取 bean,若有缓存则直接返回
- getSingleton:生成一个新的 bean 实例
- 加锁并再次从缓存中获取,与
2) getSingleton
组成双重检验锁(DCL)
- 加锁并再次从缓存中获取,与
- createBean:创建 bean 并进行一系列初始化操作,实际调用 doCreateBean 方法
- createBeanInstance:创建 bean 实例,但尚未设置属性
- addSingletonFactory:将该 bean 加入三级缓存 singletonFactories 中,提前暴露该 bean 的引用
- populateBean:为 bean 设置属性值,若遇到依赖的 bean 会调用
13) resolveValueIfNecessary
方法,内部会重新调用 getBean 方法来获取依赖 bean 的引用
由于最初要获取的 bean 已经加入三级缓存暴露了引用,所以其依赖的 bean 若出现循环引用则会调用2) getSingleton
从三级缓存中获取该引用,并把该引用转移到二级缓存中
- populateBean:为 bean 设置属性值,若遇到依赖的 bean 会调用
- initializeBean:初始化 bean
- invokeAwareMethods:调用 setBeanName、setBeanFactory、setApplicationContext 三个 aware 类型的方法
- applyBeanPostProcessorsBeforeInitialization:后置处理器的 before 方法(与初始化方法)
- invokeInitMethods:初始化方法
- applyBeanPostProcessorsAfterInitialization:后置处理器的 after 方法(后初始化方法)
- registerDisposableBeanIfNecessary:注册销毁方法
- addSingleton:初始化 bean 后将其放入一级缓存以便后续使用
2. 循环依赖
概述:
循环引用,两个或两个以上的 Bean 互相持有对方,最终形成闭环
解决:
采用三级缓存。在 bean 创建后尚未设置属性时先将 bean 的引用(称为早期引用)暴露,这样后续依赖的 bean 就能获取到这个引用。
- 因为需要将 bean 的早期引用放入缓存,所以无法解决构造器注入下的循环依赖
- 因为 prototype 原型 bean 生成实例后不被容器管理,所以也无法解决 scope 为 prototype 的 bean 循环依赖
原理:
- 三级缓存 singletonFactories:bean 实例化后尚未设置属性时放入三级缓存中提前暴露引用
- 二级缓存 earlySingletonObjects:当 A 对象依赖 B 对象,B 对象又依赖 A 对象时,B 可从三级缓存中获取 A 的早期引用,并放入二级缓存。在这过程中可做一些扩展操作
- 一级缓存 singletonObjects:存放完整初始化后的 bean
九、其他
其他资料:
面试题: