一个Bean从生到灭要经历很多过程,总体分为定义、实例化、属性赋值(依赖注入)、初始化、生存期、销毁。
如下图,是个概括图,后面重点介绍Bean的定义、初始化、销毁过程:
下面是一个细化的Bean生命周期图:
Spring对bean进行实例化,默认bean是单例;
Spring对bean进行依赖注入,Spring按照Bean定义信息配置Bean所有属性;
如果bean实现了BeanNameAware接口,spring将bean的id传给setBeanName()方法;
如果bean实现了BeanFactoryAware接口,spring将调用setBeanFactory方法,将BeanFactory实例传进来;
如果bean实现了ApplicationContextAware接口,它的setApplicationContext()方法将被调用,将应用上下文的引用传入到bean中;
如果bean实现了BeanPostProcessor接口,它的postProcessBeforeInitialization方法将被调用;
如果bean实现了InitializingBean接口,spring将调用它的afterPropertiesSet接口方法,类似的如果bean使用了init-method属性声明了初始化方法,该方法也会被调用;
如果bean实现了BeanPostProcessor接口,它的postProcessAfterInitialization接口方法将被调用;
此时bean已经准备就绪,可以被应用程序使用了,他们将一直驻留在应用上下文中,直到该应用上下文被销毁;
若bean实现了DisposableBean接口,spring将调用它的distroy()接口方法。同样的,如果bean使用了destroy-method属性声明了销毁方法,则该方法被调用;
一、Bean的定义
Bean的定义过程其实就是如何正确地将 Bean 装配到 IoC 容器中。
- Spring通过我们的配置,如@ComponentScan定义的扫描路径去找到带有@Component的类,完成资源定位的过程
一旦找到了资源,那么它就开始解析,并且将定义的信息在BeanDefinition的实现类中 保存起来 。注意 ,此时还没有初始 Bean,也就没有 Bean 实例,它有的仅仅是 Bean 定义。 然后就会把 Bean 定义发布到 Spring IoC 容器中。 此时, IoC 容器也只有 Bean 的定义,还是 没有 Bean 的实例生成。
BeanDefinition的实现类可以保存这个bean的构造函数参数、bean的Scope范围、是否是延迟初始化LazyInit、是否是Singleton等信息。
二、Bean的实例化和初始化
实例化:是对象创建的过程。比如使用构造方法new对象,为对象在内存中分配空间。是一个创建Bean的过程,即调用Bean的构造函数,单例的Bean放入单例池中。
初始化:是为对象中的属性赋值的过程,即调用Bean的setter,设置Bean的属性。
2.1 Spring什么时候实例化bean,首先要分2种情况:
第一:如果使用BeanFactory作为Spring Bean的工厂类,则所有的bean都是在第一次使用该Bean的时候实例化
第二:如果使用ApplicationContext作为Spring Bean的工厂类,则又分为以下几种情况:
(1):如果bean的scope是singleton的,并且lazy-init为false(默认是false,所以可以不用设置),则ApplicationContext启动的时候就实例化该Bean,并且将实例化的Bean放在一个map结构的缓存中,下次再使用该Bean的时候,直接从这个缓存中取
(2):如果bean的scope是singleton的,并且lazy-init为true,则该Bean的实例化是在第一次使用该Bean的时候进行实例化
(3):如果bean的scope是prototype的,则该Bean的实例化是在第一次使用该Bean的时候进行实例化
2.2 Bean的初始化
三、Bean初始化过程相关接口及方法说明
完成了一个bean的定义,并发布到IOC容器中时,这个bean是不完整的,IoC 容器也只有这个 Bean 的定义,还是没有 Bean的实例生成。这个bean并不知道自己是在哪个工厂(BeanFactory的引用)中,也不知道自己在工厂(BeanFactory)中的代号(id)。awre翻译过来是知道的,已感知的,意识到的,所以这些接口从字面意思应该是能感知到所有Aware
前面的含义。
3.1 BeanNameAware接口
作用:让Bean获取或设置自己在BeanFactory配置中的名字(根据情况是id或者name)。
实现:通过实现BeanNameAware接口,接口中就一个方法setBeanName()
public class LogginBean implements BeanNameAware {
private String beanName = null;
public void setBeanName(String beanName) {
this.beanName = beanName;
}
}
在程序中使用 BeanFactory.getBean(String beanName) 或者 applicationContext.getBean(beanName)之前,Bean的名字就已经设定好了。所以,程序中可以尽情的使用BeanName而不用担心它没有被初始化。
举个例子:
(1)定义两个User类,一个实现BeanNameAware,一个不实现。
public class User implements BeanNameAware{
private String id;
private String name;
private String address;
public void setBeanName(String beanName) {
//ID保存BeanName的值
id=beanName;
}
}
public class User implements BeanNameAware{
private String id;
private String name;
private String address;
}
(2)在Spring配置文件中初始化两个对象。
<bean id="zhangsan" class="com.github.jettyrun.springinterface.demo.aware.beannameaware.User">
<property name="name" value="zhangsan"></property>
<property name="address" value="火星"></property>
</bean>
<bean id="lisi" class="com.github.jettyrun.springinterface.demo.aware.beannameaware.User2">
<property name="name" value="lisi"></property>
<property name="address" value="木星"></property>
</bean>
(3)main方法测试
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application-beanaware.xml");
User user=context.getBean(User.class);
System.out.println(String.format("实现了BeanNameAware接口的信息BeanId=%s,所有信息=%s",user.getId(),user.toString()));
User2 user2=context.getBean(User2.class);
System.out.println(String.format("未实现BeanNameAware接口的信息BeanId=%s,所有信息=%s",user2.getId(),user2.toString()));
}
实现了BeanNameAware接口的信息BeanId=zhangsan,所有信息=User{id='zhangsan', name='zhangsan', address='火星'}
未实现BeanNameAware接口的信息BeanId=null,所有信息=User{id='null', name='lisi', address='木星'}
能够看到,我们在实现了BeanNameAware的User中,获取到了Spring容器中的BeanId(对应Spring配置文件中的Id属性),而没有实现BeanNameAware的User2,则不能获取到Spring容器中的Id属性。
所以BeanNameAware接口是为了让自身Bean能够感知到,获取到自身在Spring容器中的id属性。
同理,其他的Aware接口也是为了能够感知到自身的一些属性。比如实现了ApplicationContextAware 接口的类,能够获取到ApplicationContext,实现了BeanFactoryAware接口的类,能够获取到BeanFactory对象。
3.2 BeanFactoryAware接口
作用:让Bean获取配置他们的BeanFactory的引用,即让bean知道自己是在哪个工厂(BeanFactory的引用)中。让Bean获取配置他们的BeanFactory的引用。
实现:实现BeanFactoryAware接口,其中只有一个方法即setFactory(BeanFactory factory)。使用上与BeanNameAware接口无异,只不过BeanFactoryAware注入的是个工厂,BeanNameAware注入的是个Bean的名字。
public class ZhangSan implements BeanFactoryAware {
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("【" + this.getClass().getSimpleName() + "】调用BeanFactoryAware的setBeanFactory");
}
}
让bean获取配置自己的工厂之后,当然可以在Bean中使用这个工厂的getBean()方法,但是,实际上非常不推荐这样做,因为结果是进一步加大Bean与Spring的耦合,而且,能通过DI注入进来的尽量通过DI来注入。当然,除了查找bean,BeanFactory可以提供大量其他的功能,例如销毁singleton模式的Bean。
通过这个方法的参数创建它的BeanFactory实例,实现了BeanFactoryAware接口,就可以让Bean拥有访问Spring容器的能力。缺点:导致代码与spring的api耦合在一起。
3.3 ApplicationContext 对象
我们为什么要去获取这个ApplicationContext对象?获取到了我们能干什么呢?答:能手动从Spring获取所需要的bean。
// 获取bean 方法1
public static <T>T getBean(String beanName){
return (T) applicationContext.getBean(beanName);
}
// 获取bean 方法2
public static <T> T getBean(Class<T> c){
return (T) applicationContext.getBean(c);
}
我们一般称BeanFactory为IoC容器,而称ApplicationContext为应用上下文。如果说BeanFactory是Sping的心脏,那么ApplicationContext就是完整的身躯了。Spring的核心是容器,而容器并不唯一,框架本身就提供了很多个容器的实现,大概分为两种类型:
- 一种是不常用的BeanFactory,这是最简单的容器,只能提供基本的DI功能;
- 一种就是继承了BeanFactory后派生而来的ApplicationContext(应用上下文),它能提供更多企业级的服务,例如解析配置文本信息等等,这也是ApplicationContext实例对象最常见的应用场景。
ApplicationContext由BeanFactory派生而来,提供了更多面向实际应用的功能。在BeanFactory中,很多功能需要以编程的方式实现,而在ApplicationContext中则可以通过配置的方式实现。ApplicationContext 是 spring 中较高级的容器。和 BeanFactory 类似,它可以加载配置文件中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean。 另外,它增加了企业所需要的功能,比如,从属性文件从解析文本信息和将事件传递给所指定的监听器。
4.1.1 怎样获取得到这个ApplicationContext对象
1、手动创建ApplicationContext对象:
private LsjmUserServiceImpl user = null;
@Before
public void getBefore(){
// 这里由于我的配置文件写的目录不是根目录,所以用FileSystemXmlApplicationContext
String xmlPath = "WebContent/WEB-INF/config/base/applicationContext.xml";
ApplicationContext ac = new FileSystemXmlApplicationContext(xmlPath);
user = ac.getBean(LsjmUserServiceImpl.class);
}
// 如果是放在根目录下
public void getBefore(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
user = ac.getBean(LsjmUserServiceImpl.class);
}
这种获取方法一般用在写junit测试的时候, 实际项目中强烈不推荐使用,因为Spring容器在服务器加载的时候,就已经被初始化了,这里相当于又重新加载了一次,没有必要而且耗资源,并且非常慢。
2、通过Spring的工具类WebApplicationContextUtils 获取
/* 需要传入一个参数ServletContext对象, 获取方法在上面 */
// 这种方法 获取失败时抛出异常
ApplicationContext ac = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);
// 这种方法 获取失败时返回null
ApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(sc);
3、写一个工具类 比如BaseHolder 实现ApplicationContextAware接口
public class BaseHolder implements ApplicationContextAware {
private static ApplicationContext applicationContext;
/**
* 服务器启动,Spring容器初始化时,当加载了当前类为bean组件后,
* 将会调用下面方法注入ApplicationContext实例
*/
@Override
public void setApplicationContext(ApplicationContext arg0) throws BeansException {
System.out.println("初始化了");
BaseHolder.applicationContext = arg0;
}
public static ApplicationContext getApplicationContext(){
return applicationContext;
}
/**
* 外部调用这个getBean方法就可以手动获取到bean
* 用bean组件的name来获取bean
* @param beanName
* @return
*/
@SuppressWarnings("unchecked")
public static <T>T getBean(String beanName){
return (T) applicationContext.getBean(beanName);
}
}
4.2 ApplicationContextAware
一个Bean需要实现某个功能,但该功能必须借助于Spring容器才能实现,此时就必须让该Bean先获取Spring容器,然后借助于Spring容器实现该功能。为了让Bean获取它所在的Spring容器,可以让该Bean实现ApplicationContextAware接口。
Spring容器会检测容器中的所有Bean,如果发现某个Bean实现了ApplicationContextAware接口,Spring容器会在创建该Bean之后,自动调用该Bean的setApplicationContextAware()方法,调用该方法时,会将容器本身作为参数传给该方法——该方法中的实现部分将Spring传入的参数(容器本身)赋给该类对象的applicationContext实例变量,因此接下来可以通过该applicationContext实例变量来访问容器本身。
四、代码验证
我们还是用人类要吃饭的关系类说明Bean的生命周期。先实现Person和Food两个接口:
public interface Person {
public void live();
public void setFood(Food food);
}
public interface Food {
public void eat();
}
接着编写上面两个接口的实现类:
@Component
public class Apple implements Food {
@Override
public void eat() {
System.out.println("我是苹果,吃我吧");
}
}
public class ZhangSan implements Person, BeanNameAware, BeanFactoryAware,
ApplicationContextAware, InitializingBean, DisposableBean {
private Food food = null;
/**
* 来自Person接口
*/
@Override
public void live() {
this.food.eat();
}
/**
* 来自Person接口
*/
@Override
public void setFood(Food food) {
System.out.println("设置 food !");
this.food = food;
}
/**
* 来自BeanNameAware接口
*/
@Override
public void setBeanName(String beanName) {
System.out.println("【" + this.getClass().getSimpleName() + "】调用BeanNameAware的setBeanName");
}
/**
* 来自BeanFactoryAware接口
*/
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("【" + this.getClass().getSimpleName() + "】调用BeanFactoryAware的setBeanFactory");
}
/**
* 来自ApplicationContextAware接口
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("【" + this.getClass().getSimpleName() + "】调用ApplicationContextAware的setApplicationContext");
}
/**
* 来自InitializingBean接口
*/
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("【" + this.getClass().getSimpleName() + "】调用InitializingBean的afterPropertiesSet");
}
@PostConstruct
public void init() {
System.out.println("【" + this.getClass().getSimpleName() + "】注解@Postconstruct定义的自定义初始化方法");
}
@PreDestroy
public void destroy1() {
System.out.println("【" + this.getClass().getSimpleName() + "】注解@PreDestroy定义的自定义销毁方法");
}
/**
* 来自DisposableBean接口
*/
@Override
public void destroy() throws Exception {
System.out.println("【" + this.getClass().getSimpleName() + "】调用DisposableBean方法");
}
}
上面我们让ZhangSan实现了多个初始化需要的接口,并实现了相应的方法,来完成ZhangSan这个bean的初始化处理,为了说明自定义初始化方法,我们使用@PostConstruct注解增加了自定义初始化过程,为了说明自定义销毁方法我们使用@PreDestroy增加了自定义销毁过程,另外为了说明BeanPostProcessor接口是针对全部bean的,下面实现了这个接口:
public class BeanPostProcessorExample implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor调用postProcessBeforeInitialization参数【" + bean.getClass().getSimpleName()+"】【" + beanName + "】");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor调用postProcessAfterInitialization参数【" + bean.getClass().getSimpleName()+"】【" + beanName + "】");
return bean;
}
}
然后我们用一个类聚合下上面的bean:
@Configuration
public class Config {
@Bean
public ZhangSan initZhangSan(){
ZhangSan zhangSan = new ZhangSan();
zhangSan.setFood(new Apple());
return zhangSan;
}
@Bean
public BeanPostProcessorExample initExample(){
return new BeanPostProcessorExample();
}
}
测试类:
@SpringBootTest
class GfdemoApplicationTests {
@Test
void contextLoads() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
ctx.close();
}
}
结果:
BeanPostProcessor调用postProcessBeforeInitialization参数【Apple】【apple】
BeanPostProcessor调用postProcessAfterInitialization参数【Apple】【apple】
设置 food !
【ZhangSan】调用BeanNameAware的setBeanName
【ZhangSan】调用BeanFactoryAware的setBeanFactory
【ZhangSan】调用ApplicationContextAware的setApplicationContext
BeanPostProcessor调用postProcessBeforeInitialization参数【ZhangSan】【initZhangSan】
【ZhangSan】注解@Postconstruct定义的自定义初始化方法
【ZhangSan】调用InitializingBean的afterPropertiesSet
BeanPostProcessor调用postProcessAfterInitialization参数【ZhangSan】【initZhangSan】
【ZhangSan】注解@PreDestroy定义的自定义销毁方法
【ZhangSan】调用DisposableBean方法
可以看出bean的生命周期流程是按照上面的图进行的,另外也可以看出BeanPostProcessor接口是针对全部bean的,在初始化Apple和ZhangSan的时候都有执行。
五、spring中的循环依赖
循环依赖其实就是循环引用,也就是两个或者两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。
Spring中循环依赖场景有:
- (1)构造器的循环依赖
- (2)field属性的循环依赖
其中,构造器的循环依赖问题无法解决,只能拋出BeanCurrentlyInCreationException异常,在解决属性循环依赖时,spring采用的是提前暴露对象的方法(3级缓存)。
5.1 怎么检测是否存在循环依赖
Bean在创建的时候可以给该Bean打标,如果递归调用回来发现正在创建中的话,即说明了循环依赖了。
5.2 Spring怎么解决循环依赖
Spring的单例对象的初始化主要分为三步:
- (1)createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
- (2)populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
- (3)initializeBean:调用spring xml中的init 方法。
循环依赖主要发生在第一、二步,也就是构造器循环依赖和field循环依赖。
为什么说构造器的循环依赖问题无法解决呢?field属性的循环依赖又是怎么解决的呢?
对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存:
- (三级)singletonFactories : 单例对象工厂的cache
- (二级)earlySingletonObjects :提前暴光的单例对象的Cache
- (一级)singletonObjects:单例对象的cache
分别对应3个Map:
3个MAP
BeanFactory中有3个map
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
第一个map singletonObjects就是最终结果.BF加工完成的bean都会被丢到这里(同时清空其他2个map)
第二个map singletonFactories是一个中间过程map. 当一个bean被new,但是还没有populate他的属性,就是说还没加工完成,属性都没设置,初始化方法(init-method,afterProperties,或者BeanPostProcessor还没处理它的时候).这个bean会被丢到这个map里.也就是说,每个bean首先会被加到这个map里.当然完全初始化完成的时候就会被remove,丢到第一个map里.
第三个map earlySingletonObjects是专门用于处理循环依赖的.因为A,B循环依赖的时候,需要把A提早暴露出来set到B的属性里.所以这个map的名字也挺能看出作用的.earlySingleton..那意思就是提早暴露的单例.当A引用B再引用A的时候就会把A从singletonFactories中remove,丢到earlySingletonObjects中.(完全初始化完成以后还是会被丢到第一个map里,所以这里也相当于一个中间状态)
在创建bean的时候,首先是从cache中获取这个单例的bean,这个缓存就是singletonObjects。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,如果获取到了则:从singletonFactories中移除,并放入earlySingletonObjects中,也就是从三级缓存移动到了二级缓存。
Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache,这个cache的类型是ObjectFactory。这里就是解决循环依赖的关键,发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器),虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。
让我们来分析一下“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了C的实例对象,同时C的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A的bean对象填充属性的时候,先判断该bean对象是否为单例,并且是否允许提前暴露(一般都为true),如果条件都符合,则调用 addSingletonFactory方法,将创建改bean对象的工厂类存存放到第三级缓存registeredSingletons中,然后如果创建过程中,该bean对象完整创建,那么该对象会被从registeredSingletons移除 然后加入到一级缓存singletonObjects中。A对象装配之前,先讲自己的对象引用存放三级缓存中registeredSingletons然后,然后发现需要对象B,调用getSingleton(B)方法。
获取B的过程,先去一级缓存中查询,如果没找到,再去二级缓存,三级缓存,这个时候因为B还没有创建,所以需要创建B对象,这个时候重复之前创建A的过程,B对象先将自己的对象引用存放到registeredSingletons中 然后,装配B对象,发现这个时候需要对象C,同理,先创建C。
注意!这个时候循环引用出现了,C的创建时需要装配A的,所以调用getSingleton(A)方法,但是之前A虽然没有创建完全,不存在一级缓存singletonObjects,但是A的对象引用存在三级缓存registeredSingletons中,C获取到A之后,将A从三级缓存registeredSingletons中删除,移到二级缓存earlySingletonObjects中,然后C创建完成,放置一级缓存singletonObjects中,B也创建完成,放置一级缓存singletonObjects中,随后A也创建完成,放置一级缓存singletonObjects中。
循环引用的问题就此解决。