zoukankan      html  css  js  c++  java
  • 如果你每次面试前都要去背一篇Spring中Bean的生命周期,请看完这篇文章

    前言

    当你准备去复习Spring中Bean的生命周期的时候,这个时候你开始上网找资料,很大概率会看到下面这张图:

    image-20200707094637369

    先不论这张图上是否全面,但是就说这张图吧,你是不是背了又忘,忘了又背?

    究其原因在于,你没有理解为什么需要这些步骤,也不知道为什么要按这个顺序执行

    笔者在阅读完整个IOCAOP的源码后,希望通过这篇文章讲一讲我的Spring中Bean生命周期的看法,帮助大家能理解性的记忆整个流程,而不是死记硬背!

    基础知识补充

    所谓理解也是建立在有一定知识储备的基础上的,所以这里先补充一些基础概念

    Bean创建的三个阶段

    Spring在创建一个Bean时是分为三个步骤的

    • 实例化,可以理解为new一个对象
    • 属性注入,可以理解为调用setter方法完成属性注入
    • 初始化,你可以按照Spring的规则配置一些初始化的方法(例如,@PostConstruct注解)

    生命周期的概念

    Bean的生命周期指的就是在上面三个步骤中后置处理器BeanPostprocessor穿插执行的过程

    后置处理器的分析

    按照实现接口进行分类

    1. 直接实现了BeanPostProcessor接口

    最简单的后置处理器,也就是说直接实现了BeanPostProcessor接口,这种后置处理器只能在初始化前后执行

    public interface BeanPostProcessor {
        
    	// 初始化前执行的方法
    	@Nullable
    	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    		return bean;
    	}    
        
    	// 初始化后执行的方法
    	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    		return bean;
    	}
    
    }
    
    1. 直接实现了InstantiationAwareBeanPostProcessor接口

    在第一种后置处理的基础上进行了一层扩展,可以在Bean的实例化阶段前后执行

    // 继承了BeanPostProcessor,额外提供了两个方法用于在实例化前后的阶段执行
    // 因为实例化后紧接着就要进行属性注入,所以这个接口中还提供了一个属性注入的方法
    public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
    	
        // 实例化前执行
    	default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
    		return null;
    	}
    	
        // 实例化后置
    	default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
    		return true;
    	}
        
        // 属性注入
        default PropertyValues postProcessPropertyValues(
            PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
    
            return pvs;
        }
    }
    
    1. Spring内部专用的后置处理器

    可能有的小伙伴认为,第三种后置处理器肯定就是用来在属性注入前后执行了的吧。我只能说,大兄弟,太天真了,看看下面这张图

    image-20200707163736980

    这种情况下再为属性注入阶段专门提供两个方法是不是有点多余呢?实际上第三种后置处理器是Spring为了自己使用而专门设计的

    public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor {
    	
        // 推测bean的类型,例如在属性注入阶段我们就需要知道符合依赖类型的Bean有哪些
        @Nullable
        default Class<?> predictBeanType(Class<?> beanClass, String beanName) throws BeansException {
            return null;
        }
    	
        // 推断出所有符合要求的构造函数,在实例化对象的时候我们就需要明确到底使用哪个构造函数
        @Nullable
        default Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName)
            throws BeansException {
    
            return null;
        }
    	
        // 获取一个提前暴露的对象,用于解决循环依赖
        default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
            return bean;
        }
    
    }
    

    一般我们在探究生命周期的时候都不会考虑这种后置处理器的执行

    生命周期详细介绍

    在了解了上面的概念后,我们再来看看这张图

    image-20200707094637369

    至少现在这张图上缺少了实例化前后后置处理器的执行流程,对吧?

    再补充上这一点之后,我们再来看看,属性注入后紧接着已经是初始化的阶段,在初始化阶段开始前应该要调用BeanPostProcessor的预初始化方法(postProcessBeforeInitialization),然后调用自定义的初始化方法,最后调用postProcessAfterInitialization,这是没有问题,但是为什么要在初始前还要调用Aware接口的方法,如果你看了源码的话可能会说,源码就是这么写的,别人就是这么设计的,但是为什么要这么设计呢?我们看源码到一定阶段后不应该仅仅停留在是什么的阶段,而应该多思考为什么,这样能帮助你更好的了解这个框架

    那么为什么Aware接口非要在初始化前执行呢?

    这样做的目的是因为,初始化可能会依赖Aware接口提供的状态,例如下面这个例子

    @Component
    public class A implements InitializingBean, ApplicationContextAware {
    
        ApplicationContext applicationContext;
    
        @Override
        public void afterPropertiesSet() throws Exception {
            // 初始化方法需要用到ApplicationContextAware提供的ApplicationContext
            System.out.println(applicationContext);
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }
    }
    

    这种情况下Aware接口当然要在初始化前执行啦!

    另外,在讨论Bean的初始化的时候经常会碰到下面这个问题,@PostConstruct,afterPropertiesSet跟XML中配置的init-method方法的执行顺序。

    请注意,@PostConstruct实际上是在postProcessBeforeInitialization方法中处理的,严格来说它不属于初始化阶段调用的方法,所以这个方法是最先调用的

    其次我们思考下是调用afterPropertiesSet方法的开销大还是执行配置文件中指定名称的初始化方法开销大呢?我们不妨用伪代码演示下

    // afterPropertiesSet,强转后直接调用
    ((InitializingBean) bean).afterPropertiesSet()
        
    // 反射调用init-method方法
    // 第一步:找到这个方法
    Method method = class.getMethod(methodName)
    // 第二步:反射调用这个方法
    method.invoke(bean,null)
    

    相比而言肯定是第一种的效率高于第二种,一个只是做了一次方法调用,而另外一个要调用两次反射。

    因此,afterPropertiesSet的优先级高于XML配置的方式

    所以,这三个方法的执行顺序为:

    1. @PostConstruct注解标注的方法
    2. 实现了InitializingBean接口后复写的afterPropertiesSet方法
    3. XML中自定义的初始化方法

    在完成初始化,没什么好说的了,最后调用一下postProcessAfterInitialization,整个Bean的生命周期到此结束

    总结

    本文的主要目的是想要帮助大家更好的理解整个Bean的生命周期,不过理解是建立在有一定知识存储的基础上的

    你至少要对Bean的后置处理器跟Bean创建有一个大概的理解,那么通过本文你能理清一些细节方面的东西

    例如,为什么Aware接口执行在初始化阶段之前?为什么初始化的三个方法会按

    @PostConstructafterPropertiesSet,XML中定义的初始化方法这个顺序执行。

    本文也将是我整个Spring关于IOCAOP的最后一篇文字,在这之后我打算做一个Spring事务专题,预计6到7篇文章,事务结束后关于整个Spring源码的学习也就结束啦!本来预期要一年才能完成,不过因为最近离职了,所以打算全职在家写完这个系列!

    如果本文对你由帮助的话,记得点个赞吧!也欢迎关注我的公众号,微信搜索:程序员DMZ,或者扫描下方二维码,跟着我一起认认真真学Java,踏踏实实做一个coder。

    公众号

    我叫DMZ,一个在学习路上匍匐前行的小菜鸟!

  • 相关阅读:
    IE浏览器和谷歌浏览器相互跳转
    centos7安装docker
    centos7安装groovy
    centos7安装NodeJs
    mongodb数据库的备份还原
    centos7最小版配置
    centos7中python2.7升级到python3.7
    typedef struct用法详解与小结
    MinGW的gdb调试
    MinGW-w64安装教程——著名C/C++编译器GCC的Windows版本
  • 原文地址:https://www.cnblogs.com/daimzh/p/13303528.html
Copyright © 2011-2022 走看看