好记性不如烂笔头,做记录亦是复习。加油
笔记:
Spring介绍与核心思想(IOC/AOP)
自定义注解实现IOC容器笔记
AOP介绍与源码剖析
BeanFactory是Spring框架中IOC容器的顶层接口,只是用来定义⼀些基础功能,定义⼀些基础规范,而ApplicationContext是它的⼀个子接口,所以ApplicationContext是具备BeanFactory提供的全部功能。
可以称BeanFactory为SpringIOC的基础容器,ApplicationContext为容器的高级接口,比BeanFactory要拥有更多的功能,比如说国际化支持和资源访问(xml,java配置类)等等。
Java环境下启动IoC容器
ClassPathXmlApplicationContext:从类的根路径下加载配置文件
FileSystemXmlApplicationContext:从磁盘路径上加载配置文件
AnnotationConfigApplicationContext:纯注解模式下启动Spring容器
Web环境下启动IoC容器
1.从xml启动容器
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--配置Spring ioc容器的配置⽂件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--使⽤监听器启动Spring的IOC容器-->
<listener>
<listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass>
</listener>
</web-app>
2.从配置类启动容器
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--告诉ContextloaderListener知道我们使⽤注解的⽅式启动ioc容器-->
<context-param>
<param-name>contextClass</param-name>
<paramvalue>org.springframework.web.context.support.AnnotationConfigWebAppli
cationContext</param-value>
</context-param>
<!--配置启动类的全限定类名-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.lagou.edu.SpringConfig</param-value>
</context-param>
<!--使⽤监听器启动Spring的IOC容器-->
<listener>
<listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass>
</listener>
</web-app>
在spring框架管理Bean对象的创建时,Bean对象默认都是单例的,但是它支持配置的方式改变作用范围。
单例模式:singleton
对象出生:当创建容器时,对象就被创建了。
对象活着:只要容器在,对象一直活着。
对象死亡:当销毁容器时,对象就被销毁了。
⼀句话总结:单例模式的bean对象生命周期与容器相同。
多例模式:prototype
对象出生:当使用对象时,创建新的对象实例。
对象活着:只要对象在使用中,就⼀直活着。
对象死亡:当对象长时间不用时,被java的垃圾回收器回收了。
一句话总结:多例模式的bean对象,spring框架只负责创建,不负责销毁。
id: 用于给bean提供⼀个唯⼀标识。在⼀个标签内部,标识必须唯一。
class: 用于指定创建Bean对象的全限定类名。
name: 用于给bean提供⼀个或多个名称。多个名称用空格分隔。
factory-bean: 用于指定创建当前bean对象的工厂bean的唯⼀标识。当指定了此属性之后,class属性失效。
factory-method: 用于指定创建当前bean对象的工厂方法,如配合factory-bean属性使用,则class属性失效。如配合class属性使用,则方法必须是static的。
scope: 用于指定bean对象的作用范围。通常情况下就是singleton。当要用到多例模式时,可以配置为prototype。
init-method: 用于指定bean对象的初始化方法,此方法会在bean对象装配后调用。必须是⼀个无参方法。
destory-method: 用于指定bean对象的销毁方法,此方法会在bean对象销毁前执行。它只能为scope是singleton时起作用。
构造函数注入:利用带参构造函数实现对类成员的数据赋值。
依赖注入的配置实现之构造函数注入,就是利用构造函数实现对类成员的赋值。类中提供的构造函数参数个数必须和配置的参数个数一致,且数据类型匹配。同时要注意的是,当没有无参构造时,则必须提供构造函数参数的注入,否则Spring框架会报错。
使用构造函数注入的标签是 constructor-arg ,该标签有如下属性:
name:用于给构造函数中指定名称的参数赋值。
index:用于给构造函数中指定索引位置的参数赋值。
value:用于指定基本类型或者String类型的数据。
ref:用于指定其他Bean类型的数据。写的是其他bean的唯一标识。
set方法注入:通过类成员的set方法实现数据的注入。
依赖注入的配置实现之set方法注入,就是利用字段的set方法实现赋值的注入方式。此种方式在实际开发中是使用最多的注入方式
使用set方法注入使用 property 标签,该标签属性如下:
name:指定注入时调用的set方法名称。(注:不包含set这三个字母,druid连接池指定属性名称)
value:指定注入的数据。它支持基本类型和String类型。
ref:指定注入的数据。它支持其他bean类型。写的是其他bean的唯⼀标识
基本类型和String:注入的数据类型是基本类型或者是字符串类型的数据。
其他Bean类型:注入的数据类型是对象类型,称为其他Bean的原因是,这个对象是要求出现在IoC容器中的。那么针对当前Bean来说,就是其他Bean了。
复杂类型(集合类型):注入的数据类型是Aarry,List,Set,Map,Properties中的⼀种类型
@Configuration 注解,表名当前类是⼀个配置类
@ComponentScan 注解,替代 context:component-scan
@PropertySource,引入外部属性配置文件
@Import 引入其他配置类
@Value 对变量赋值,可以直接赋值,也可以使用 ${} 读取资源配置文件中的信息
@Bean 将方法返回对象加入 SpringIOC 容器
Bean的延迟加载(延迟创建)
ApplicationContext 容器的默认行为是在启动服务器时将所有 singleton bean 提前进行实例化。提前实例化意味着作为初始化过程的一部分,ApplicationContext 实例会创建并配置所有的singleton bean。
<bean id="testBean" class="cn.njm.LazyBean" />
该bean默认的设置为:
<bean id="testBean" calss="cn.njm.LazyBean" lazy-init="false" />
lazy-init为false则立即加载,表示在spring启动时,立刻进行实例化。
如果不想让⼀个singleton bean 在 ApplicationContext实现初始化时被提前实例化,
那么可以将bean设置为延迟实例化。
<bean id="testBean" calss="cn.njm.LazyBean" lazy-init="true" />
lazy-init为true的 bean 将不会在 ApplicationContext 启动时提前被实例化,
而是第⼀次向容器通过 getBean 索取 bean 时实例化的。
如果⼀个设置了立即加载的 bean1,引用了⼀个延迟加载的 bean2 ,那么 bean1 在容器启动时被实例化,而 bean2 由于被 bean1 引用,所以也被实例化,这种情况也符合延时加载的 bean 在第⼀次调用时才被实例化的规则。
也可以在容器层次中通过在元素上使用 “default-lazy-init” 属性来控制延时初始化。如下面配置:
<beans default-lazy-init="true">
<!-- no beans will be eagerly pre-instantiated... -->
</beans>
如果⼀个 bean 的 scope 属性为 scope=“pototype” 时,即使设置了 lazy-init=“false”,容器启动时也不会实例化bean,而是调用 getBean 方法实例化。
应用场景
1.开启延迟加载⼀定程度提高容器启动和运转性能。
2.对于不常使用的 Bean 设置延迟加载,这样偶尔使用的时候再加载,不必要从一开始该 Bean 就占用资源。
BeanFactory接口是容器的顶级接口,定义了容器的⼀些基础行为,负责生产和管理Bean的⼀个工厂,具体使用其实是使用它下面的一些子接口类型,比如ApplicationContext;
Spring中Bean有两种,⼀种是普通Bean,⼀种是工厂Bean(FactoryBean),FactoryBean可以生成某⼀个类型的Bean实例(返回给我们),也就是说我们可以借助于它自定义Bean的创建过程。FactoryBean使用较多,尤其在Spring框架⼀些组件中会使用,还有其他框架和Spring框架整合时使用。
// 可以让我们⾃定义Bean的创建过程(完成复杂Bean的定义)
public interface FactoryBean<T> {
@Nullable
// 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器
的单例对象缓存池中Map
T getObject() throws Exception;
@Nullable
// 返回FactoryBean创建的Bean类型
Class<?> getObjectType();
// 返回作⽤域是否单例
default boolean isSingleton() {
return true;
}
}
//用到的地方
public class CompanyFactoryBean implements FactoryBean<Company> {
...........
}
//获取FactoryBean产⽣的对象
Object companyBean = applicationContext.getBean("companyBean");
System.out.println("bean:" + companyBean);
//获取FactoryBean,在获取FactoryBean的时候需要在id之前添加“&”
Object companyBean = applicationContext.getBean("&companyBean");
System.out.println("bean:" + companyBean);
Spring提供了两种后处理bean的扩展接口,分别为 BeanPostProcessor 和BeanFactoryPostProcessor,两者在使用上是有所区别的。
BeanFactoryPostProcessor:在BeanFactory初始化之后可以使用BeanFactoryPostProcessor进行后置处理做⼀些事情
BeanPostProcessor:在Bean对象实例化(并不是Bean的整个生命周期完成)之后可以使用BeanPostProcessor进行后置处理做一些事情
注意: 对象不一定是springbean,而springbean一定是个对象
路径:package org.springframework.beans.factory.config;
BeanPostProcessor是针对Bean级别的处理,可以针对某个具体的Bean
该接口提供了两个方法,分别在Bean的初始化方法前和初始化方法后执行,具体这个初始化方法指的是什么方法,类似在定义bean时,定义了init-method所指定的方法。
定义一个类实现了BeanPostProcessor,默认是会对整个Spring容器中所有的bean进行处理。如果要对具体的某个bean处理,可以通过方法参数判断,两个类型参数分别为Object和String,第一个参数是每个bean的实例,第二个参数是每个bean的name或者id属性的值。所以可以通过第二个参数,来判断将要处理的具体的bean。
注意: 处理是发生在Spring容器的实例化和依赖注入之后。
路径:package org.springframework.beans.factory.config;
BeanFactory级别的处理,是针对整个Bean的工厂进行处理
此接口只提供了一个方法,方法参数为ConfigurableListableBeanFactory,该参数类型定义了一些方法
其中有个方法名为getBeanDefinition的方法,可以根据此方法,找到我们定义bean 的BeanDefinition对象。然后可以对定义的属性进行修改,以下是BeanDefinition中的方法
方法名字类似bean标签的属性,setBeanClassName对应bean标签中的class属性,所以当拿到BeanDefifinition对象时,可以手动修改bean标签中所定义的属性值。
BeanDefifinition对象:在 XML 中定义的 bean标签,Spring 解析 bean 标签成为一个 JavaBean,这个JavaBean 就是 BeanDefifinition
注意: 调用 BeanFactoryPostProcessor 方法时,这时候bean还没有实例化,此时 bean 刚被解析成BeanDefifinition对象
注意: Spring 为 Bean 提供了细致全面的生命周期过程,通过实现特定的接口或 bean>标签的属性设置,都可以对 Bean 的生命周期过程产生影响。虽然可以随意配置 的属性,但是建议不要过多地使用 Bean 实现接口,因为这样会导致代码和 Spring 的聚合过于紧密。
好处: 提高培养代码架构思维、深入理解框架
原则:
定焦原则:抓主线
宏观原则:站在上帝视角,关注源码结构和业务流程(淡化具体某行代码的编写细节)读源码的方法和技巧
断点(观察调用栈)
反调(Find Usages)
经验(spring框架中doXXX,做具体处理的地方)
Spring源码构建步骤:
1.从github下载源码
2.安装gradle 5.6.3(类似于maven) Idea 2019.1 Jdk 11.0.5
3.导入(耗费⼀定时间)
4.编译工程(顺序:core-oxm-context-beans-aspects-aop)工程—>Tasks—>other—>compileTestJava
IOC容器是Spring的核心模块,抽象了对象管理、依赖关系管理的框架解决方案。Spring 提供了很多的容器,其中 BeanFactory 是顶层容器(根容器),不能被实例化,它定义了所有 IOC 容器 必须遵从的⼀套原则,具体的容器实现可以增加额外的功能,比如常用到的ApplicationContext,更具体的实现比如 ClassPathXmlApplicationContext 包含了解析 xml 等⼀系列的内容,AnnotationConfigApplicationContext 则是包含了注解解析等⼀系列的内容。IOC容器继承体系非常聪明,需要使用哪个层次用哪个层次即可,不必使用功能大而全的。
BeanFactory 顶级接口方法栈如下:
BeanFactory 容器继承体系:
通过其接口语设计,可以看到使用的 ApplicationContext 除了继承BeanFactory的子接口,还继承了ResourceLoader、MessageSource等接口,因此其提供的功能也就更丰富了。
以 ClasspathXmlApplicationContext 为例,深入源码查看 IOC 容器的初始化流程。
思路:创建⼀个类 LagouBean ,让其实现几个特殊的接口,并分别在接口实现的构造器、接口方法中断点,观察线程调用栈,分析出 Bean 对象创建和管理关键点的触发时机。
LagouBean类
public class LagouBean implements InitializingBean {
/**
* 构造函数
*/
public LagouBean() {
System.out.println("LagouBean 构造器...");
}
/**
* InitializingBean 接⼝实现
*/
public void afterPropertiesSet() throws Exception {
System.out.println("LagouBean afterPropertiesSet...");
}
}
BeanPostProcessor 接口实现类
public class MyBeanPostProcessor implements BeanPostProcessor {
public MyBeanPostProcessor() {
System.out.println("BeanPostProcessor 实现类构造函数...");
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if ("lagouBean".equals(beanName)) {
System.out.println("BeanPostProcessor 实现类
postProcessBeforeInitialization ⽅法被调⽤中......");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if ("lagouBean".equals(beanName)) {
System.out.println("BeanPostProcessor 实现类
postProcessAfterInitialization ⽅法被调⽤中......");
}
return bean;
}
}
BeanFactoryPostProcessor 接口实现类
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
public MyBeanFactoryPostProcessor() {
System.out.println("BeanFactoryPostProcessor的实现类构造函数...");
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory
beanFactory) throws BeansException {
System.out.println("BeanFactoryPostProcessor的实现⽅法调⽤中......");
}
}
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 https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="lagouBean" class="com.lagou.LagouBean"/>
<bean id="myBeanFactoryPostProcessor" class="com.lagou.MyBeanFactoryPostProcessor"/>
<bean id="myBeanPostProcessor" class="com.lagou.MyBeanPostProcessor"/>
</beans>
IOC 容器源码分析用例
/**
* Ioc 容器源码分析基础案例
*/
@Test
public void testIoC(){
ApplicationContext applicationContext=new
ClassPathXmlApplicationContext("classpath:applicationContext.xml");
LagouBean lagouBean=applicationContext.getBean(LagouBean.class);
System.out.println(lagouBean);
}
根据断点调试,可以发现,在未设置延迟加载的前提下,Bean 的创建是在容器初始化过程中完成的。
观察调用栈:
通过如上观察,可以发现构造函数的调用时机在AbstractApplicationContext类refresh方法的finishBeanFactoryInitialization(beanFactory)处;
观察调用栈:
通过如上观察,可以发现 InitializingBean中afterPropertiesSet 方法的调用时机也是在AbstractApplicationContext类refresh方法的finishBeanFactoryInitialization(beanFactory);
分别在构造函数、postProcessBeanFactory 方法处打断点,观察调用栈发现BeanFactoryPostProcessor 初始化在AbstractApplicationContext类refresh方法的invokeBeanFactoryPostProcessors(beanFactory);
postProcessBeanFactory 调用在AbstractApplicationContext类refresh方法的invokeBeanFactoryPostProcessors(beanFactory);
分别在构造函数、postProcessBeanFactory 方法处打断点,观察调用栈发现BeanPostProcessor 初始化在AbstractApplicationContext类refresh方法的registerBeanPostProcessors(beanFactory);
postProcessBeforeInitialization 调用在AbstractApplicationContext类refresh方法的finishBeanFactoryInitialization(beanFactory);
postProcessAfterInitialization 调用在AbstractApplicationContext类refresh方法的finishBeanFactoryInitialization(beanFactory);
根据上面的调试分析,可以发现 Bean对象创建的几个关键时机点代码层级的调用都在AbstractApplicationContext 类 的 refresh 方法中,可见这个方法对于Spring IOC 容器初始化来说相当关键。
汇总如下:
关键点 | 触发代码 |
---|---|
构造器 | refresh#finishBeanFactoryInitialization(beanFactory)(beanFactory) |
BeanFactoryPostProcessor 初始化 | refresh#invokeBeanFactoryPostProcessors(beanFactory) |
BeanFactoryPostProcessor 方法调用 | refresh#invokeBeanFactoryPostProcessors(beanFactory) |
BeanPostProcessor 初始化 | registerBeanPostProcessors(beanFactory) |
BeanPostProcessor 方法调用 | refresh#finishBeanFactoryInitialization(beanFactory) |
由上分析可知,Spring IOC 容器初始化的关键环节就在AbstractApplicationContext#refresh() 方法中,可以查看 refresh 方法来俯瞰容器创建的主体流程。
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 第⼀步:刷新前的预处理
prepareRefresh();
/*
第⼆步:
获取BeanFactory;默认实现是DefaultListableBeanFactory
加载BeanDefition 并注册到 BeanDefitionRegistry
*/
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 第三步:BeanFactory的预准备⼯作(BeanFactory进⾏⼀些设置,⽐如context的类加载器等)
prepareBeanFactory(beanFactory);
try {
// 第四步:BeanFactory准备⼯作完成后进⾏的后置处理⼯作
postProcessBeanFactory(beanFactory);
// 第五步:实例化并调⽤实现了BeanFactoryPostProcessor接⼝的Bean
invokeBeanFactoryPostProcessors(beanFactory);
// 第六步:注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执⾏
registerBeanPostProcessors(beanFactory);
// 第七步:初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
initMessageSource();
// 第⼋步:初始化事件派发器
initApplicationEventMulticaster();
// 第九步:⼦类重写这个⽅法,在容器刷新的时候可以⾃定义逻辑
onRefresh();
// 第⼗步:注册应⽤的监听器。就是注册实现了ApplicationListener接⼝的监听器bean
registerListeners();
/*
第⼗⼀步:
初始化所有剩下的⾮懒加载的单例bean
初始化创建⾮懒加载⽅式的单例Bean实例(未设置属性)
填充属性
初始化⽅法调⽤(⽐如调⽤afterPropertiesSet⽅法、init-method⽅法)
调⽤BeanPostProcessor(后置处理器)对实例bean进⾏后置处
*/
finishBeanFactoryInitialization(beanFactory);
/*
第⼗⼆步:
完成context的刷新。主要是调⽤LifecycleProcessor的onRefresh()⽅法,并且发布事
件 (ContextRefreshedEvent)
*/
finishRefresh();
}
......
}
}
Resource定位: 指对BeanDefinition的资源定位过程。通俗讲就是找到定义Javabean信息的XML文件,并将其封装成Resource对象。
BeanDefinition载入 : 把用户定义好的Javabean表示为IOC容器内部的数据结构,这个容器内部的数据结构就是BeanDefinition。
注册BeanDefinition到 IOC 容器
1.子流程入口在 AbstractRefreshableApplicationContext#refreshBeanFactory 方法中
2.依次调用多个类的 loadBeanDefinitions 方法 —> AbstractXmlApplicationContext —>AbstractBeanDefinitionReader —> XmlBeanDefinitionReader ⼀直执行到XmlBeanDefinitionReader 的 doLoadBeanDefinitions 方法。
这里重点观察XmlBeanDefinitionReader 类的 registerBeanDefinitions 方法,期间产生了多次重载调用,定位到最后⼀个
此处关注两个地方:⼀个createRederContext方法,⼀个是DefaultBeanDefinitionDocumentReader类的registerBeanDefinitions方法,先进入createRederContext 方法看看
可以看到,此处 Spring 首先完成了 NamespaceHandlerResolver 的初始化。再进入 registerBeanDefinitions 方法中追踪,调用了DefaultBeanDefinitionDocumentReader#registerBeanDefinitions 方法
进入 doRegisterBeanDefinitions 方法
进入 parseBeanDefinitions 方法
进入 parseDefaultElement 方法
进入 processBeanDefinition 方法
至此,注册流程结束,发现所谓的注册就是把封装的 XML 中定义的 Bean信息封装为BeanDefinition 对象之后放⼊⼀个Map中,BeanFactory 是以 Map 的结构组织这些 BeanDefinition的。
可以在DefaultListableBeanFactory中看到此Map的定义
/** Map of bean definition objects, keyed by bean name. */
private final Map<String, BeanDefinition> beanDefinitionMap = new
ConcurrentHashMap<>(256);
1.通过上面之前的关键时机点分析,得知Bean创建子流程入口在AbstractApplicationContext#refresh()方法的finishBeanFactoryInitialization(beanFactory) 处
2.进入finishBeanFactoryInitialization
3.继续进入DefaultListableBeanFactory类的preInstantiateSingletons方法,找到下面部分的代码,看到工厂Bean或者普通Bean,最终都是通过getBean的方法获取实例
4.继续跟踪下去,进入到了AbstractBeanFactory类的doGetBean方法,这个方法中的代码很多,直接找到核心部分
5.接着进入到AbstractAutowireCapableBeanFactory类的方法,找到以下代码部分
6. 进入doCreateBean方法看看,该方法关注两块重点区域
(1) 创建Bean实例,此时尚未设置属性
(2) 给Bean填充属性,调用初始化方法,应用BeanPostProcessor后置处理器
普通 Bean 的初始化是在容器启动初始化阶段执行的,而被lazy-init=true修饰的 bean 则是在从容器里第一次进行context.getBean() 时进行触发。Spring 启动的时候会把所有bean信息(包括XML和注解)解析转化成Spring能够识别的BeanDefinition并存到Hashmap里供下面的初始化时用,然后对每个BeanDefinition 进行处理,如果是懒加载的则在容器初始化阶段不处理,其他的则在容器初始化阶段进行初始化并依赖注入。
public void preInstantiateSingletons() throws BeansException {
// 所有beanDefinition集合
List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
// 触发所有⾮懒加载单例bean的初始化
for (String beanName : beanNames) {
// 获取bean 定义
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
// 判断是否是懒加载单例bean,如果是单例的并且不是懒加载的则在容器创建时初始化
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// 判断是否是 FactoryBean
if (isFactoryBean(beanName)) {
final FactoryBean<?> factory = (FactoryBean<?>)
getBean(FACTORY_BEAN_PREFIX + beanName);
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
return ((SmartFactoryBean<?>) factory).isEagerInit();
}
}, getAccessControlContext());
}
} else {
/*
如果是普通bean则进⾏初始化并依赖注⼊,此 getBean(beanName)接下来触发的逻辑和
懒加载时 context.getBean("beanName") 所触发的逻辑是⼀样的
*/
getBean(beanName);
}
}
}
}
对于被修饰为lazy-init的bean Spring容器初始化阶段不会进行 init 并且依赖注入,而是当第一次进行getBean时候才进行初始化并依赖注入。
对于非懒加载的bean,getBean的时候会从缓存里头获取,因为容器初始化阶段 Bean 已经初始化完成并缓存了起来了。
循环依赖其实就是循环引用,也就是两个或者两个以上的 Bean 互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。
注意: 这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是⼀个死循环,除非有终结条件。
Spring中循环依赖场景有:
构造器的循环依赖(构造器注入)
Field 属性的循环依赖(set注入)
其中,构造器的循环依赖问题无法解决,只能拋出 BeanCurrentlyInCreationException 异常,在解决属性循环依赖时,spring采用的是提前暴露对象的方法。
1.单例 bean 构造器参数循环依赖(无法解决)
2.prototype 原型 bean循环依赖(无法解决)
对于原型bean的初始化过程中不论是通过构造器参数循环依赖还是通过setXxx方法产生循环依赖,Spring都会直接报错处理。
AbstractBeanFactory.doGetBean()方法:
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
return (curVal != null &&
(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>)
curVal).contains(beanName))));
}
在获取bean之前如果这个原型bean正在被创建则直接抛出异常。原型bean在创建之前会进行标记这个beanName正在被创建,等创建结束之后会删除标记。
try {
//创建原型bean之前添加标记
beforePrototypeCreation(beanName);
//创建原型bean
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
//创建原型bean之后删除标记
afterPrototypeCreation(beanName);
}
总结:Spring 不支持原型 bean 的循环依赖。
3.单例bean通过setXxx或者@Autowired进行循环依赖
Spring 的循环依赖的理论依据基于 Java 的引用传递,当获得对象的引用时,对象的属性是可以延后设置的,但是构造器必须是在获取引用之前Spring通过setXxx或者@Autowired方法解决循环依赖其实是通过提前暴露⼀个ObjectFactory对象来完成的,简单来说ClassA在调用构造器完成对象初始化之后,在调用ClassA的setClassB方法之前就把ClassA实例化的对象通过ObjectFactory提前暴露到Spring容器中
(1) Spring容器初始化ClassA通过构造器初始化对象后提前暴露到Spring容器。
boolean earlySingletonExposure = (mbd.isSingleton() &&
this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
//将初始化后的对象提前已ObjectFactory对象注⼊到容器中
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
(2) ClassA调用setClassB方法,Spring首先尝试从容器中获取ClassB,此时ClassB不存在Spring容器中。
(3)Spring容器初始化ClassB,同时也会将ClassB提前暴露到Spring容器中。
(4)ClassB调用setClassA方法,Spring从容器中获取ClassA ,因为第一步中已经提前暴露了ClassA,因此可以获取到ClassA实例(ClassA通过spring容器获取到ClassB,完成了对象初始化操作)。
这样ClassA和ClassB都完成了对象初始化操作,解决了循环依赖问题。