Spring IOC基础
spring主要有3中配置方式:
- 纯xml,bean信息定义全部配置在xml中。
- xml+注解部分bean使用xml定义,部分bean使用注解定义。
- 纯注解模式所以bean都使用注解来定义。
我们会使用beans.xml来定义需要实例化对象的类的全限定类名已经类之间依赖关系描述。BeanFactory是IOC容器通过反射技术来实例化对象并维护对象之间的依赖关系。
在采用了注解的方式创建spring时,javase 应用使用ApplicationContext appplicationContext = new ClassPathXmlApplication("beans.xml");或者new FileSystemXmlApplicationContext("c:/beans.xml");创建spring。javaweb 使用ContextLoaderListener(监听器去加载xml)。
在采用纯注解的方式创建spring时,javase 应用使用ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class); javaweb 使用ContextLoaderListener(监听器去加载注解配置类)。
BeanFactory和ApplicationContext区别
BeanFactory是Spring框架中IOC的容器顶层接口,只用来定义一些基础功能,而ApplicationContext是它的一个子接口,所以ApplicationContext是具备BeanFactory提供的全部功能的。
BeanFactory为SpringIOC基础容器,ApplicationContext是容器的高级接口,比BeanFactory要拥有更多的功能,比如支持国际化和资源访问等等。

Java环境下启动IOC容器:
- ClassPathXmlApplicationContext:从类的根路径下加载配置文件(推荐使用)。
- FileSystemXmlApplicationContext:从磁盘路径上加载配置文件。
- AnnotationConfigApplicationContext:纯注解模式下启动Spring容器。
web环境下启动IOC容器:
-
从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> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app> -
从配置类启动容器
<!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> <context-param> <param-name>contextClass</param-name> <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value> </context-param> <context-param> <param-name>contextConfigLocation</param-name> <param-value>com.lagou.edu.SpringConfig</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
纯xml模式
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三种方式:
-
使用无参构造器,默认情况下,会通过反射调用无参构造器函数来创建对象。如果类中没有无参构造函数,将创建失败。
<bean id="userService" class="com.guslegend.service.impl.TransferServiceImpl"> </bean> -
使用静态方法创建,使用static修饰的方法。
<bean id="userService" class="com.guslegend.factory.BeanFactory" factory-method="getTransferService"></bean>
-
使用实例化方式创建。
<bean id="beanFactory" class="com.guslegend.factory.instancemethod.BeanFactory"></bean>
<bean id="transferService" factory-bean="beanFactory" factormethod="getTransferService"></bean>
Bean的声明周期:在spring框架管理Bean对象的创建时,Bean对象默认是单例的,但是它支持配置的方式改变作用范围。日常开发中我们最多的作用范围就是singleton(单例模式)和prototype(原型模式,也叫多例模式)。
<!--配置service对象-->
<bean id="transferService"
class="com.guslegend.service.impl.TransferServiceImpl" scope="singleton">
</bean>
singleton:对象出生(容器创建时,对象就被创建);对象活着(只要容器存在,对象就一直活着);对象死亡(当容器销毁,对象就被销毁了)。
prototype:对象出生(当使用对象时,就一直活着);对象活着(只要对象在使用,就一直活着);对象死亡(当对象长时间不使用,将被java的垃圾回收器回收了)。
Bean标签属性:
- 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时起作用。
DI依赖注入的xml配置:
- 按照注入的方式分类:构造器注入,set方法注入(使用最多)。
- 按照注入的数据类型分类:基本类型和String,复杂类型(集合类型)。
依赖注入的配置使用构造函数注入,类中提供的构造函数参数个数必须和配置的参数个数一致,且数据类型匹配。同时需要注意的是,当没有无参构造时,则必须提供构造函数参数的注入,否则Spring框架会报错。


在使用构造函数注入 时,涉及的标签constructor-arg,该标签有如下属性:
- name:用于给构造函数中指定名称的参数赋值。
- index:用于给够着函数中指定索引位置的参数赋值。
- value:用于指定基本类型或者String类型的数据。
- ref:用于指定其他Bean类型的数据,写的是其他bean的唯一标识。


使用set方法实现赋值的注入方式,此种方式在实际开发中使用最多的注入方式。使用此种方式注入property标签,该标签属性如下:
- name:用于给构造函数中指定名称的参数赋值。
- value:用于指定基本类型或者String类型的数据。
- ref:用于指定其他Bean类型的数据,写的是其他bean的唯一标识。
注解和xml相结合模式
xml中标签与注解的对应
|-------------------|-----------------------------------------------------------------------------------------------------------|
| XML形式 | 注解形式 |
| 标签 | @Component("accountDao",如果不写括号里面的,默认为类的类名首字母小写)。此外其有三种别名@Controller,@Service,@Repository分别用于控制层,服务层,dao层。 |
| 标签中的scope属性 | @Scope("prototype"),默认为单例 |
| 标签中的init-method属性 | @PostConstruct,初始化后调用的方法 |
| 标签中destory-method | @PreDestory,销毁前调用的方法 |
DI依赖注入的注解实现方式
@Autowired(推荐使用),需要导入org.springframework.beans.factory.annotation.Autowired;
public class TransferServiceImpl {
@Autowired
private AccountDao accountDao;
}
当一个类型有多个bean值的时候,会造成无法选择具体注入哪一个的情况,这个时候我们需要配合使用@Qualifier使用。
public class TransferServiceImpl {
@Autowired
@Qualifier("myBean")
private AccountDao accountDao;
}
@Resouce,由j2EE提供,需要导入 javax.annotation.Resource,其默认按照ByName自动注入
public class TransferService {
@Resource
private AccountDao accountDao;
@Resource(name="studentDao")
private StudentDao studentDao;
@Resource(type="TeacherDao")
private TeacherDao teacherDao;
@Resource(name="manDao",type="ManDao")
private ManDao manDao;
}
- 如果同时指定了 name 和 type,则从 Spring 上下文中找到唯一匹配的 bean 进行装配,找不到则抛出异常。
- 如果指定了 name,则从上下文中查找名称(id)匹配的 bean 进行装配,找不到则抛出异常。
- 如果指定了 type,则从上下文中找到类型匹配的唯一 bean 进行装配,找不到或是找到多个,都会抛出异常。
- 如果既没有指定 name,又没有指定 type,则自动按照 byName 方式进行装配;
注意在jdk11中@Resource已经被移除如果需要使用,要单独引入jar包
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
纯注解方式
@Configuration注解,表名当前类是一个配置类。
@ ComponentScan注解,替代context:component-scan。
@PropertySource,引入外部属性配置文件
@Import引入其他配置文件
@Value对变量赋值,可以直接赋值,也可以使用${}读取资源配置文件中的信息
@Bean将方法返回对象加入SpringIOC容器
Spring IOC高级特性
lazy-Init延迟加载
ApplicationContext容器的默认行为是在启动服务器是将所有singleton bean提取进行实例化。会创建并配置所以的singletonbean。
<bean id="testBean" class="cn.guslegend.LazyBean" />
该bean默认的设置为:
<bean id="testBean" calss="cn.guslegend.LazyBean" lazy-init="false" />
lazy-init = "false",标识立即加载,表示在spring启动时,立刻进行实例化。
将lazy-true设置为ture的bean不会再ApplicationContext启动时提取被实例化,那么可以将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方法实例化的。
应用场景:
- 开启延迟加载一定程度提高容器启动和运转性能。
- 对于不常使用的bean设置延迟加载,这些偶尔使用的时候再加载,不必要从一开始该Bean就占用资源。
FactoryBean和BeanFactory
BeanFactory接口是容器的顶级接口,定义了容器的一些基础行为,负责生成和管理Bean的一个工厂,具体使用它下面的子接口类型,比如ApplicationContext;
Spring中的Bean有两种类型,一种是普通Bean,一种是工厂Bean,FactoryBean可以生成某个类型Bean实例(返回给我们),也就是说我们可以借助于它自定义Bean的创建过程。
Bean创建的三种凡是中的静态方式和实例化方法和FactoryBean作用类似,FactoryBean使用较多,尤其在Spring框架一些组件中会使用,还有其他框架和Spring框架整合时使用。
// 可以让我们⾃定义Bean的创建过程(完成复杂Bean的定义)
public interface FactoryBean<T> {
/**
* 返回FactoryBean创建的Bean实例,如果 isSingleton 返回true,则该实例会放到Spring容器
的单例对象缓存池中Map
*/
@Nullable
T getObject() throws Exception;
/**
* 返回FactoryBean创建的Bean类型
*/
@Nullable
Class<?> getObjectType();
/**
* 返回作⽤域是否单例
*/
default boolean isSingleton() {
return true;
}
}
测试获取FactoryBean产生的对象
Object companyBean = applicationContext.getBean("companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:Company{name='guslegend', address='中关村', scale=500}
测试获取FactoryBean,需要在id千添加"&"
Object companyBean = applicationContext.getBean("&companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:com.lagou.edu.factory.CompanyFactoryBean@53f6fd09
后置处理器
Spring提供了两种后置处理Bean的扩展接口,分别BeanPostProcessor和BeanFactoryPostProcessor,两者在使用上是有区别的。
工厂初始化BeanFactory ->Bean对象。
在BeanFactory初始化后可以使用BeanFactoryPostProcessor进行后置处理 做一些事情。
在Bean对象实例化(并不是Bean整个生命周期完成)之后可以使用BeanPostProcessor进行后置处理做一些事情。
注意:对象不一定是springBean,而springBean一定是一个对象。
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
该接口的提供两个方法,分别在Bean的初始化方法前和初始化方法后执行,具体这个初始化方法指的是什么方法,类似我们在定义bean时,定义了init-method所指定的方法。
定义了一个类实现BeanPostProcessor,默认是会对整个Spring容器中所有的bean进行处理。如果要对具体某个bean处理,可以通过方法参数判断,两个类型参数分别为Object和String,第一个参数是每个bean的实例,第二个参数是每个bean的name或者id属性的值。所有我们可以通过第二个参数,来判断我们将要处理的具体的bean。
注意:处理时发送在Spring容器的实例化和依赖注入之后。
@FunctionalInterface
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
此接口只提供了一个方法,方法参数为ConfigurableListableBeanFactory,该参数类型定义了一些方法。

在这里面有个方法叫做getBeanDefinition的方法,我们可以根据此方法,找到我们定义bean的BeanDefinition对象。然后我们可以对定义的属性进行修改,以下时BeanDefinition中的方法。

方法名字类似我们bean标签的属性,setBeanClassName对应bean标签中的class属性,所有当我们拿到BeanDefinition对象时,我们可以手动修改bean标签中定义的属性。
BeanDefinition对象:我们在xml中定义bean标签,Spring解析bean标签成为一个JavaBean,这个JavaBean就是BeanDefinition。
注意:调用BeanFactoryPostProcessor方法时,这时候bean还没有实例化,此时bean刚被解析成BeanDefinition对象。
Spring IOC源码剖析
Spring IOC的容器初始化主题流程
SpringIOC的容器体系
springFacotry 顶级接口方法栈:

BeanFactory容器继承体系

Bean生命周期关键时机点
- Bean的创建是在容器初始化还是在getBean时?未设计延迟加载前提下,Bean创建是在容器初始化过程中完成的。
- 构造函数调用情况?构造函数的调用时机是在
AbstractApplicationContext类refresh方法的finishBeanFactoryInitialization(beanFactory)处; - 分析initialzingBean之afterPropertiesSet初始化方法调用情况?
InitializingBean中afterPropertiesSet⽅法的调⽤时机也是在AbstractApplicationContext类refresh⽅法的finishBeanFactoryInitialization(beanFactory); - 分析BeanFactoryPostProcessor初始化和调用情况?
BeanFactoryPostProcessor初始化在AbstractApplicationContext类refresh⽅法的invokeBeanFactoryPostProcessors(beanFactory);postProcessBeanFactory调⽤在AbstractApplicationContext类refresh⽅法invokeBeanFactoryPostProcessors(beanFactory); - 分析BeanPostProcessor初始化和调⽤情况?
BeanPostProcessor初始化在AbstractApplicationContext类refresh⽅法的registerBeanPostProcessors(beanFactory);postProcessBeforeInitialization调⽤在AbstractApplicationContext类refresh⽅法的finishBeanFactoryInitialization(beanFactory);postProcessAfterInitialization调⽤在AbstractApplicationContext类refresh⽅法的finishBeanFactoryInitialization(beanFactory);
总结:
|-------------------------------|------------------------------------------------------|
| 关键点 | 触发代码 |
| 构造器 | refresh#finishBeanFactoryInitialization(beanFactory) |
| BeanFactoryPostProcessor 初始化 | refresh#invokeBeanFactoryPostProcessors(beanFactory) |
| BeanFactoryPostProcessor 方法调用 | refresh#invokeBeanFactoryPostProcessors(beanFactory) |
| BeanPostProcessor 初始化 | registerBeanPostProcessors(beanFactory) |
| BeanPostProcessor 方法调用 | refresh#finishBeanFactoryInitialization(beanFactory) |
Spring IOC 容器初始化主流程
SpringIOC容器初始化的关键环节就在 AbstractApplicationContext#refresh() 方法中,我们查看refresh方法来俯瞰容器创建的主体流程,主体流程下的具体子流程我们后面再来讨论。
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 第一步:刷新前的预处理
prepareRefresh();
/*
* 第二步:
* 获取BeanFactory;默认实现是DefaultListableBeanFactory
* 加载BeanDefinition 并注册到 BeanDefinitionRegistry
*/
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();
} catch (BeansException ex) {
// 标准实现中会捕获异常并处理(销毁已创建的bean、重置active状态)
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// 销毁已创建的单例bean以避免资源占用
destroyBeans();
// 重置active标志
cancelRefresh(ex);
// 抛出异常
throw ex;
} finally {
// 标准实现中的收尾工作(重置Spring核心的公共缓存)
resetCommonCaches();
}
}
}
BeanFactory创建流程
获取BeanFactory子流程

BeanDefinition加载解析及注册子流程
(1)该⼦流程涉及到如下⼏个关键步骤
Resource定位:指对BeanDefinition的资源定位过程。通俗讲就是找到定义Javabean信息的XML⽂
件,并将其封装成Resource对象。
BeanDefinition载⼊ :把⽤户定义好的Javabean表示为IoC容器内部的数据结构,这个容器内部的数
据结构就是BeanDefinition。
注册BeanDefinition到 IoC 容器
(2)过程分析
Step 1:⼦流程⼊⼝在 AbstractRefreshableApplicationContext#refreshBeanFactory ⽅法中

Step 2 **:**依次调⽤多个类的 loadBeanDefinitions ⽅法 ---> AbstractXmlApplicationContext --->
AbstractBeanDefinitionReader ---> XmlBeanDefinitionReader ⼀直执⾏到
XmlBeanDefinitionReader 的 doLoadBeanDefinitions ⽅法

Step 3 **:**我们重点观察XmlBeanDefinitionReader 类的 registerBeanDefinitions ⽅法,期间产⽣了多
次重载调⽤,我们定位到最后⼀个

此处我们关注两个地⽅:⼀个createRederContext⽅法,⼀个是
DefaultBeanDefinitionDocumentReader类的registerBeanDefinitions⽅法,先进⼊
createRederContext ⽅法看看

我们可以看到,此处 Spring ⾸先完成了 NamespaceHandlerResolver 的初始化。
我们再进⼊ registerBeanDefinitions ⽅法中追踪,调⽤了
DefaultBeanDefinitionDocumentReader#registerBeanDefinitions ⽅法

进⼊ 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);

Bean创建流程
通过最开始的关键时机点分析,我们知道Bean创建⼦流程⼊⼝在 AbstractApplicationContext#refresh()⽅法的finishBeanFactoryInitialization(beanFactory) 处。

进⼊finishBeanFactoryInitialization

继续进⼊DefaultListableBeanFactory类的preInstantiateSingletons⽅法,我们找到下⾯部分的 代码,看到⼯⼚Bean或者普通Bean,最终都是通过getBean的⽅法获取实例

继续跟踪下去,我们进⼊到了AbstractBeanFactory类的doGetBean⽅法,这个⽅法中的代码很 多,我们直接找到核⼼部分

进⼊doCreateBean⽅法看看,该⽅法我们关注两块重点区域

lazy-init延迟加载机制原理
普通 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 = getMergedBeanDefinition(beanName);
// 判断是否是抽象类,不是抽象的并且是单例的并且不是懒加载的则在容器创建时初始化
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 {
isEagerInit = (factory instanceof SmartFactoryBean && ((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
// 如果是普通bean则进行初始化并依赖注入,此 getBean(beanName) 接下来触发的逻辑
// 和 懒加载时 context.getBean("beanName") 所触发的逻辑是一样的
getBean(beanName);
}
} else {
getBean(beanName);
}
}
}
}
总结
- 对于被修饰为lazy-init的bean Spring 容器初始化阶段不会进⾏ init 并且依赖注⼊,当第⼀次进⾏getBean时候才进⾏初始化并依赖注⼊ 。
- 对于⾮懒加载的bean,getBean的时候会从缓存⾥头获取,因为容器初始化阶段 Bean 已经初始化完成并缓存了起来。
Spring IOC 循环依赖问题

Spring中循环依赖场景有:
- 构造器的循环依赖(构造器注⼊)
- Field 属性的循环依赖(set注⼊)
其中,构造器的循环依赖问题⽆法解决,只能拋出 BeanCurrentlyInCreationException 异常,在解决属性循环依赖时,spring采⽤的是提前暴露对象的⽅法。
- 单例 bean 构造器参数循环依赖(⽆法解决)
- prototype 原型 bean循环依赖(⽆法解决)
对于原型bean的初始化过程中不论是通过构造器参数循环依赖还是通过setXxx⽅法产⽣循环依 赖,Spring都 会直接报错处理。
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 的循环依赖。
- 循环依赖场景:单例 bean 通过 @Autowired/@Resource 形成循环依赖(如 ClassA 依赖 ClassB,ClassB 依赖 ClassA)。
- Spring 循环依赖处理原理:基于 Java 引用传递 + 提前暴露对象引用实现:
-
- 获取对象引用时,对象的属性可延后设置,但构造器必须在获取引用前完成;
- 核心方案:通过提前暴露一个 ObjectFactory 对象,将未完全初始化的对象引用提前注册到容器中。
- 具体流程(以 ClassA 依赖 ClassB 为例):
-
- Spring 容器初始化 ClassA:对 ClassA 完成对象初始化(调用构造器);在调用 ClassA.setClassB () 前,将 ClassA 实例通过 ObjectFactory 提前暴露到 Spring 容器中。
- ClassA 调用 setClassB ():Spring 尝试从容器中获取 ClassB(此时 ClassB 未初始化)。
- Spring 容器初始化 ClassB:对 ClassB 完成对象初始化;同时将 ClassB 提前暴露到 Spring 容器中。
- ClassB 调用 setClassA ():Spring 从容器中获取 ClassA(因前期已提前暴露,可获取到 ClassA 实例)。
- 完成初始化:ClassB 完成属性注入,成为完整对象;ClassA 拿到 ClassB 实例,完成属性注入,解决循环依赖。
- 核心代码片段(提前暴露对象引用):
vb
`// 判断是否允许提前暴露单例对象(单例 + 允许循环依赖 + 正在创建中)
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);
}
});
}`