什么是循环依赖?
什么情况下循环依赖可以被处理?
Spring是如何解决的循环依赖?
只有在setter方式注入的情况下,循环依赖才能解决(错)
三级缓存的目的是为了提高效率(错)
什么是循环依赖?
从字面上来理解就是A依赖B的同时B也依赖了A
体现到代码层次就是这个样子
@Component
public class A {
// A中注入了B
@Autowired
private B b;
}
@Component
public class B {
// B中也注入了A
@Autowired
private A a;
}
当然,这是最常见的一种循环依赖,比较特殊的还有
// 自己依赖自己
@Component
public class A {
// A中注入了A
@Autowired
private A a;
}
虽然体现形式不一样,但是实际上都是同一个问题----->循环依赖
对象引用循环依赖在某些业务场景上可能是合理存在的,但是由于Spring容器设计了依赖注入机制,即Spring容器在创建bean实例化以后就要给bean中的属性自动赋值,要全部自动赋值之后,才能交给用户使用。如果出现循环依赖的情况,以两个bean互相依赖的情况作为举例,假设有AService已经实例化(但未完成初始化),但是AService中需要自动赋值的BService并没有初始化,如果Spring立刻初始化BService,发现BService中需要自动赋值AService也没有初始化完成,这样就会出现相互等待,形成死循环,可能导致Spring容器都无法启动了。
由此可见,对Bean的填充属性是循环依赖源头的开始。
为了容易理解Spring解决循环依赖过程,先简单看下Spring容器创建Bena的主要流程。
spring将内部管理的诸多对象称为一个个bean,而这些bean的创建流程大致分为两个大阶段:
spring 容器预热阶段
bean实际创建阶段
下面本人将从上述两个阶段来阐述bean的完整创建过程。
容器预热阶段
-
思路
在对象进行创建之前,spring容器需要了解所创建的对象的信息,才能在后续阶段根据了解的信息创建bean对象。这些信息即是实际工作中我们为对象所写的配置信息,它们一般以xml文件、properties文件和注解的形式存在于我们的项目之中。一个以xml文件为存储形式的bean配置文件如下:
<bean id="person" class="com.yuansu.pojo.Person" name="personAlias">
<property name="name" value="yuansu"/>
<property name="dog" ref="alan"/>
<property name="cat" ref="katie"/>
</bean>
因此,在容器预热阶段,spring将会读取配置文件,并将bean的必要信息存储在自己容器内部,用于后续对象创建。
-
BeanDefination:bean对象配置信息的存储形式
那么,spring内部是用什么样的形式来存储bean的配置信息的呢?我们知道,在java中,万物皆对象,spring选择用java对象的形式存储上述的bean配置信息,而这个对象名字就是BeanDefination。BeanDefination中存储了包括属性、构造方法参数、依赖的 Bean 名称及是否单例、延迟加载等信息:
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
// 单例、原型标识符
String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
// 标识 Bean 的类别,分别对应 用户定义的 Bean、来源于配置文件的 Bean、Spring 内部的 Bean
int ROLE_APPLICATION = 0;
int ROLE_SUPPORT = 1;
int ROLE_INFRASTRUCTURE = 2;
// 设置、返回 Bean 的父类名称
void setParentName(@Nullable String parentName);
String getParentName();
// 设置、返回 Bean 的 className
void setBeanClassName(@Nullable String beanClassName);
String getBeanClassName();
// 设置、返回 Bean 的作用域
void setScope(@Nullable String scope);
String getScope();
// 设置、返回 Bean 是否懒加载
void setLazyInit(boolean lazyInit);
boolean isLazyInit();
...
}
此处我们仅需了解BeanDefination是bean对象配置信息的java类封装,更深知识暂不做讨论。
-
BeanDefinationReader:将xml等文件配置信息读取为BeanDefination
xml等形式的配置信息转化为BeanDefination自然不是眨个眼就能直接完成转化的,需要通过某种工具对其进行读取并且转化。这里的工具即是我们此处提出的BeanDefinationReader。BeanDefinationReader是一个接口,其不同的实现类对不同形式的配置信息进行读取并封装为BeanDefination。下图是BeanDefinationReader的结构图:
如上图所示,XmlBeanDefinationReader可以读取xml文件类型的配置信息并存储为BeanDefination。其他Reader以此类推。
-
BeanFactoryPostRegistry:BeanDefination的存储仓库
当BeanDefinationReader将配置文件读取并存储到BeanDefination中后,Spring需要通过bean的id寻找到对应的BeanDefination从而获取其配置信息。这种通过Bean定义的id找到对象的BeanDefination的对应关系或者说映射关系又是如何保存的呢?这就引出了BeanDefinationRegistry了。Spring通过BeanDefinationReader将配置元信息加载到内存生成相应的BeanDefination之后,就将其注册到BeanDefinationRegistry中,BeanDefinationRegistry就是一个存放BeanDefination的仓库,它也是一种键值对的形式,通过特定的Bean定义的id,映射到相应的BeanDefination。
-
BeanFactoryPostProcessor:对BeanDefination进行扩展处理
BeanFactoryPostProcessor是容器启动阶段Spring提供的一个扩展点,主要负责对注册到BeanDefinationRegistry中的一个个的BeanDefination进行一定程度上的修改与替换。例如我们的配置元信息中有些可能会修改的配置信息散落到各处,不够灵活,修改相应配置的时候比较麻烦,这时我们可以使用占位符的方式来配置。例如配置Jdbc的DataSource连接的时候可以这样配置:
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="maxIdle" value="${jdbc.maxIdle}"></property>
<property name="maxActive" value="${jdbc.maxActive}"></property>
<property name="maxWait" value="${jdbc.maxWait}"></property>
<property name="minIdle" value="${jdbc.minIdle}"></property>
<property name="driverClassName"
value="${jdbc.driverClassName}">
</property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
BeanFactoryPostProcessor就会对注册到BeanDefinationRegistry中的BeanDefination做最后的修改,替换$占位符为配置文件中的真实的数据。
5. 总结
上述5点即是容器预热阶段的几个部分,下面我们使用图片的形式回顾上述流程:
Bean创建阶段
-
Bean生命周期:
实例化 -> 属性赋值 -> 初始化 -> 使用 -> 销毁 -
实例化:
目的:Spring将转化BeanDefinition中BeanDefinition为实例Bean(放在包装类BeanWrapper中)。
(1) 实例化时机
容器启动阶段与Bean实例化阶段存在多少时间差,Spring把这个决定权交给了程序员自己进行决定。Bean创建时间有两种策略:懒加载和非懒加载。
懒加载(isLazyInit):直到我们伸手向Spring要依赖对象实例之前,Bean都是以BeanDefinationRegistry中的一个个的BeanDefination的形式存在,也就是Spring只有在我们需要依赖对象的时候才开启相应对象的实例化阶段。
非懒加载:容器启动阶段完成之后,将立即启动Bean实例化阶段,通过隐式的调用所有依赖对象的getBean方法来实例化所有配置的Bean并保存起来。
(2)实例化的三种方式
Spring中Bean的实例化本质其实就是JVM中java实例对象的加载-连接-初始化过程。
1,使用类构造器实例化(无参构造函数)
直接通过Spring工厂返回类的实例对象。
2,使用静态工厂方法实例化(简单工厂模式)
Spring工厂调用自定义工厂的静态方法返回类的实例对象。
3,使用实例工厂方法实例化(工厂方法模式)
Spring工厂调用工厂的普通方法(非静态方法)返回类的实例对象。
(3)BeanWrapper:Bean对象的封装外壳
Spring中的Bean并不是以一个个的本来模样存在的,由于Spring IOC容器中要管理多种类型的对象,因此为了统一对不同类型对象的访问,Spring给所有创建的Bean实例穿上了一层外套,这个外套就是BeanWrapper。
BeanWrapper实际上是对反射相关API的简单封装,使得上层使用反射完成相关的业务逻辑大大的简化,我们要获取某个对象的属性,调用某个对象的方法,现在不需要在写繁杂的反射API了以及处理一堆麻烦的异常,直接通过BeanWrapper就可以完成相关操作,非常方便。
-
属性赋值
目的:上一步创建出来的对象还是个空白对象,需要为其设置属性以及依赖对象。对于基本类型的属性:如果配置元信息中有配置,那么将直接使用配置元信息中的设置值赋值即可,即使基本类型的属性没有设置值,那么得益于JVM对象实例化过程,属性依然可以被赋予默认的初始化零值。 对于引用类型的属性:Spring会将所有已经创建好的对象放入一个Map结构中,此时Spring会检查所依赖的对象是否已经被纳入容器的管理范围之内,也就是Map中是否已经有对应对象的实例了。如果有,那么直接注入,如果没有,那么Spring会暂时放下该对象的实例化过程,转而先去实例化依赖对象,再回过头来完成该对象的实例化过程。
- 初始化
目的:在交付bean之前做一些处理
- 初始化
(1)检测对象是否实现aware接口
A 、概念:
aware接口为Bean对象提供了解Spring容器本身的能力,aware系列接口增强了Spring bean的功能,但是也会造成对Spring框架的绑定,增大了与Spring框架的耦合度。(Aware是“意识到的,察觉到的”的意思,实现了Aware系列接口表明:可以意识到、可以察觉到)
aware接口有以下顺序:
B、aware接口特点:
都以“Aware”结尾
都是Aware接口的子接口,即都继承了Aware接口
接口内均定义了一个set方法
C、使用方式:一个Bean对象想要获得spring容器某个部分的引用作为自己成员变量进行使用,就需要实现上述某个接口,并声明相关的成员变量来接收。
/**
* 实现了
* ApplicationContextAware
* BeanClassLoaderAware
* BeanFactoryAware
* BeanNameAware
* 接口
* @author dengp
*
*/
public class User implements ApplicationContextAware,BeanClassLoaderAware,BeanFactoryAware,BeanNameAware{
private int id;
private String name;
// 保存感知的信息
private String beanName;
// 保存感知的信息
private BeanFactory beanFactory;
// 保存感知的信息
private ApplicationContext ac;
// 保存感知的信息
private ClassLoader classLoader;
public BeanFactory getBeanFactory() {
return beanFactory;
}
public ApplicationContext getAc() {
return ac;
}
public ClassLoader getClassLoader() {
return classLoader;
}
public User(){
System.out.println("User 被实例化");
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBeanName() {
return beanName;
}
/**
* 自定义的初始化方法
*/
public void start(){
System.out.println("User 中自定义的初始化方法");
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", beanName=" + beanName + "]";
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
System.out.println(">>> setBeanClassLoader");
this.classLoader = classLoader;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println(">>> setApplicationContext");
this.ac = applicationContext;
}
@Override
public void setBeanName(String name) {
System.out.println(">>> setBeanName");
this.beanName = name;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println(">>> setBeanFactory");
this.beanFactory = beanFactory;
}
}
D、 注意
Spring IOC容器大体可以分为两种:
BeanFactory:提供IOC思想所设想所有的功能,同时也融入AOP等相关功能模块,可以说BeanFactory是Spring提供的一个基本的IOC容器。
ApplicationContext:构建于BeanFactory之上,同时提供了诸如容器内的时间发布、统一的资源加载策略、国际化的支持等功能,是Spring提供的更为高级的IOC容器。
对于BeanFactory来说,这一步的实现是先检查相关的Aware接口,然后去Spring的对象池(也就是容器,也就是那个Map结构)中去查找相关的实例(例如对于ApplicationContextAware接口,就去找ApplicationContext实例),也就是说我们必须要在配置文件中或者使用注解的方式,将相关实例注册容器中,BeanFactory才可以为我们自动注入。
对于ApplicationContext来说,由于其本身继承了一系列的相关接口,所以当检测到Aware相关接口,需要相关依赖对象的时候,ApplicationContext完全可以将自身注入到其中。
- BeanPostProcessor前置处理
目的:BeanPostProcessor前置处理就是在要生产的Bean实例放到容器之前,允许我们程序员对Bean实例进行一定程度的修改,替换等操作。
前面讲到的ApplicationContext对于Aware接口的检查与自动注入就是通过BeanPostProcessor实现的,在这一步Spring将检查Bean中是否实现了相关的Aware接口,如果是的话,那么就将其自身注入Bean中即可。
Spring中AOP就是在这一步实现的偷梁换柱,产生对于原生对象的代理对象,然后将对源对象上的方法调用,转而使用代理对象的相同方法调用实现的。
- 自定义初始化逻辑
初始化有两种方式,实现InitializingBean接口或者配置init-method参数。
注意:在两者同时存在时,实现InitializingBean接口的afterpropertiesSet()方法执行顺序在配置init-method参数的方法前。
-
实现InitializingBean接口:InitializingBean是Spring提供的拓展性接口,InitializingBean接口为bean提供了属性赋值后初始化的处理方法,它只有一个afterPropertiesSet方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法。
-
配置init-method参数:通过init-method参数指定某个方法为初始化方法,然后此方法就会在bean初始化的时候执行。
xml形式:
<bean id="myBean" class="com.yuansu.MyBean" init-method="myInit"></bean>
注解形式:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BeanConfig {
@Bean(initMethod = "myInit", destroyMethod = "myDestroy")
public MyBean myBean(){
MyBean mybean = new MyBean();
return myBean;
}
}
-
BeanPostProcessor后置处理
与前置处理类似,这里是在Bean自定义逻辑也执行完成之后,Spring又留给我们的最后一个扩展点。我们可以在这里在做一些我们想要的扩展。 -
自定义销毁逻辑
销毁有两种方式,实现DisposableBean接口或者配置init-method参数。 -
实现DisposableBean接口:类似初始化实现InitializingBean接口。
-
配置destroy-method参数:类似初始化配置init-method参数。
-
使用
这个时候可以对bean对象进行正常使用。 -
销毁
Spring的Bean在为我们服务完之后,马上就要消亡了(通常是在容器关闭的时候),别忘了我们的自定义销毁逻辑,这时候Spring将以回调的方式调用我们自定义的销毁逻辑,然后Bean就这样走完了光荣的一生!