Sping中bean的生命周期(代码展示、循环依赖)

一个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 容器中。

  1. Spring通过我们的配置,如@ComponentScan定义的扫描路径去找到带有@Component的类,完成资源定位的过程
  2. 一旦找到了资源,那么它就开始解析,并且将定义的信息在BeanDefinition的实现类中 保存起来 。注意 ,此时还没有初始 Bean,也就没有 Bean 实例,它有的仅仅是 Bean 定义。
  3. 然后就会把 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 实例的 生成,更没有完成依赖注入。在默认的情况下,s pring 会继续去完成 Bean  实例化和依赖注入,这 样从 IoC 容器中就可以得到一个依赖注入完成的 Bean 但是,有些 Bean 会受到变化因素 的影响,这时我们希望是取出 Bean 的时候完成初始化和依赖注入,换句话说就是让那些 Bean 只是将定义发布到 IoC 容器而不做实例化和依赖注入, 当我 们取出来的时候才做初始化和依赖注入等操作,也就是延迟初始化Bean。 @ComponentScan 中还有一 个配 置项 lazy I nit ,只可以配 Boolean 值,且默认值为 false ,也就是 默认不进行延迟初始化,因此在默认的情况下 Sp ring 会对 Bea 进行实例化和依赖注入对应的属性值。
 
如果仅仅是实例化和依赖注入还是比 较简单 的,还不能完 成进行自定义的要求, 为了完成依赖注入的功能 Spring 在完成依赖注入之后 ,还提供了 列的 接口和配置来完成  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的核心是容器,而容器并不唯一,框架本身就提供了很多个容器的实现,大概分为两种类型:

  1. 一种是不常用的BeanFactory,这是最简单的容器,只能提供基本的DI功能;
  2. 一种就是继承了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中。

循环引用的问题就此解决。

猜你喜欢

转载自blog.csdn.net/weixin_41231928/article/details/107039437