• Spring框架中Bean的生命周期及加载顺序


    Spring 容器中的 Bean 是有生命周期的

    Spring 允许在 Bean 在初始化完成后以及 Bean 销毁前执行特定的操作,常用的设定方式有以下三种:

    • 通过实现 InitializingBean/DisposableBean 接口来定制初始化之后/销毁之前的操作方法;
    • 通过 <bean> 元素的 init-method/destroy-method属性指定初始化之后 /销毁之前调用的操作方法;
    • 在指定方法上加上@PostConstruct 或@PreDestroy注解来制定该方法是在初始化之后还是销毁之前调用。 

    这是我们就有个疑问,这三种方式是完全等同的吗,孰先孰后?

     

    一、简单介绍

    1、init-method方法,初始化bean的时候执行,可以针对某个具体的bean进行配置。init-method需要在applicationContext.xml配置文档中bean的定义里头写明。例如:<bean id="TestBean" class="nju.software.xkxt.util.TestBean" init-method="init"></bean>

    这样,当TestBean在初始化的时候会执行TestBean中定义的init方法。

    2、afterPropertiesSet方法,初始化bean的时候执行,可以针对某个具体的bean进行配置。afterPropertiesSet 必须实现 InitializingBean接口。实现 InitializingBean接口必须实现afterPropertiesSet方法。

    3、BeanPostProcessor,针对所有Spring上下文中所有的bean,可以在配置文档applicationContext.xml中配置一个BeanPostProcessor,然后对所有的bean进行一个初始化之前和之后的代理。BeanPostProcessor接口中有两个方法: postProcessBeforeInitialization和postProcessAfterInitialization。 postProcessBeforeInitialization方法在bean初始化之前执行, postProcessAfterInitialization方法在bean初始化之后执行。

    总之,afterPropertiesSet 和init-method之间的执行顺序是afterPropertiesSet 先执行,init-method 后执行。从BeanPostProcessor的作用,可以看出最先执行的是postProcessBeforeInitialization,然后是afterPropertiesSet,然后是init-method,然后是postProcessAfterInitialization。

    下面我们将带着这个疑问,试图通过测试代码以及分析Spring源码找到答案。

     

    首先,我们还是编写一个简单的测试代码:

    Java代码  
    
    public class InitSequenceBean implements InitializingBean {  
       
        public InitSequenceBean() {  
           System.out.println("InitSequenceBean: constructor");  
        }  
         
        @PostConstruct  
        public void postConstruct() {  
           System.out.println("InitSequenceBean: postConstruct");  
        }  
         
        public void initMethod() {  
           System.out.println("InitSequenceBean: init-method");  
        }  
         
        @Override  
        public void afterPropertiesSet() throws Exception {  
           System.out.println("InitSequenceBean: afterPropertiesSet");  
        }  
    }  
      

    并且在配置文件中添加如下Bean定义:

    <bean class="InitSequenceBean" init-method="initMethod"></bean>

     

    好了,我们启动Spring容器,观察输出结果,就可知道三者的先后顺序了:

    InitSequenceBean: constructor

    InitSequenceBean: postConstruct

    InitSequenceBean: afterPropertiesSet

    InitSequenceBean: init-method

     

    通过上述输出结果,三者的先后顺序也就一目了然了:

    Constructor > @PostConstruct > InitializingBean > init-method

     

    先大致分析下为什么会出现这些的结果:构造器(Constructor)被率先调用毋庸置疑,InitializingBean先于init-method我们也可以理解(在也谈Spring容器的生命周期中已经讨论过),但是PostConstruct为何率先于InitializingBean执行呢?

     

    我们再次带着这个疑问去查看Spring源代码来一探究竟。

     

    通过Debug并查看调用栈,我们发现了这个类org.springframework.context.annotation.CommonAnnotationBeanPostProcessor,从命名上,我们就可以得到某些信息——这是一个BeanPostProcessor。想到了什么?在也谈Spring容器的生命周期中,我们提到过BeanPostProcessor的postProcessBeforeInitialization是在Bean生命周期中afterPropertiesSet和init-method之前执被调用的。

     

    再次观察CommonAnnotationBeanPostProcessor这个类,它继承自InitDestroyAnnotationBeanPostProcessor。InitDestroyAnnotationBeanPostProcessor顾名思义,就是在Bean初始化和销毁的时候所作的一个前置/后置处理器。

     

    通过查看InitDestroyAnnotationBeanPostProcessor类下的postProcessBeforeInitialization方法:

    Java代码  
    
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {  
           LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());  
           try {  
               metadata.invokeInitMethods(bean, beanName);  
           }  
           catch (InvocationTargetException ex) {  
               throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException());  
           }  
           catch (Throwable ex) {  
               throw new BeanCreationException(beanName, "Couldn't invoke init method", ex);  
           }  
            return bean;  
        }  

      查看findLifecycleMetadata方法,继而我们跟踪到buildLifecycleMetadata这个方法体中,看下buildLifecycleMetadata这个方法体的内容:

    
    Java代码  
    
    private LifecycleMetadata buildLifecycleMetadata(final Class clazz) {  
           final LifecycleMetadata newMetadata = new LifecycleMetadata();  
           final boolean debug = logger.isDebugEnabled();  
           ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {  
               public void doWith(Method method) {  
                  if (initAnnotationType != null) {  
                      if (method.getAnnotation(initAnnotationType) != null) {  
                         newMetadata.addInitMethod(method);  
                         if (debug) {  
                             logger.debug("Found init method on class [" + clazz.getName() + "]: " + method);  
                         }  
                      }  
                  }  
                  if (destroyAnnotationType != null) {  
                      if (method.getAnnotation(destroyAnnotationType) != null) {  
                         newMetadata.addDestroyMethod(method);  
                         if (debug) {  
                             logger.debug("Found destroy method on class [" + clazz.getName() + "]: " + method);  
                         }  
                      }  
                  }  
               }  
           });  
           return newMetadata;  
    }  
      

    分析这段代码发现,在这里会去判断某方法有没有被initAnnotationType/destroyAnnotationType注释,如果有,则添加到init/destroy队列中,后续一一执行。

     

    initAnnotationType/destroyAnnotationType注释是什么呢,我们在CommonAnnotationBeanPostProcessor的构造函数中看到下面这段代码:

    Java代码  收藏代码

    public CommonAnnotationBeanPostProcessor() {  
           setOrder(Ordered.LOWEST_PRECEDENCE - 3);  
           setInitAnnotationType(PostConstruct.class);  
           setDestroyAnnotationType(PreDestroy.class);  
           ignoreResourceType("javax.xml.ws.WebServiceContext");  
    }  
      

    一切都清晰了吧。一言以蔽之,@PostConstruct注解后的方法在BeanPostProcessor前置处理器中就被执行了,所以当然要先于InitializingBean和init-method执行了。

     

    最后,给出本文的结论,Bean在实例化的过程中:

    Constructor > @PostConstruct > InitializingBean > init-method

     

    本文源代码下载:https://lb-multi-demo.googlecode.com/svn/trunk/spring-lifecycle-test

     

     

    加载顺序为:

    先构造函数——>然后是b的set方法注入——>InitializingBean   的afterPropertiesSet方法——>init-method方法

    一、Spring装配Bean的过程   
    1. 实例化;  
    2. 设置属性值;  
    3. 如果实现了BeanNameAware接口,调用setBeanName设置Bean的ID或者Name;  
    4. 如果实现BeanFactoryAware接口,调用setBeanFactory 设置BeanFactory;  
    5. 如果实现ApplicationContextAware,调用setApplicationContext设置ApplicationContext  
    6. 调用BeanPostProcessor的预先初始化方法;  
    7. 调用InitializingBean的afterPropertiesSet()方法;  
    8. 调用定制init-method方法;  
    9. 调用BeanPostProcessor的后初始化方法;  
    
    
    Spring容器关闭过程   
    1. 调用DisposableBean的destroy();  
    2. 调用定制的destroy-method方法;

    总结为:

    以下内容是从书中摘录来的,但是我发现即使摘录一遍,对其内容的理解也会更加深入!  

    ① Spring IoC容器找到关于Bean的定义并实例化该Bean。
    ② Spring IoC容器对Bean进行依赖注入。
    ③ 如果Bean实现了BeanNameAware接口,则将该Bean的id传给setBeanName方法。
    ④ 如果Bean实现了BeanFactoryAware接口,则将BeanFactory对象传给setBeanFactory方法。
    ⑤ 如果Bean实现了BeanPostProcessor接口,则调用其postProcessBeforeInitialization方法。
    ⑥ 如果Bean实现了InitializingBean接口,则调用其afterPropertySet方法。
    ⑦ 如果有和Bean关联的BeanPostProcessors对象,则这些对象的postProcessAfterInitialization方法被调用。
    ⑧ 当销毁Bean实例时,如果Bean实现了DisposableBean接口,则调用其destroy方法。

    BeanPostProcessor是创建每个类时都会去执行的一个接口,postProcessBeforeInitialization是在类初始化之前调用的一个方法,创建的对象的引用会指向改方法的返回值对象。

    调用过程示例如下:

    ClassA classA = new ClassA();
    classA = postProcessBeforeInitialization(classA, "classA");


    所以我们可以通过该方法就可以实现动态替换我们的bean。

    @Component
    public class LocalProcessor implements BeanPostProcessor {
     
        @Autowired
        private DefaultListableBeanFactory defaultListableBeanFactory;
     
        private String targetBeanName = "test22";
     
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            if (StringUtils.endsWithIgnoreCase(beanName, targetBeanName)) {
                boolean containsBean = defaultListableBeanFactory.containsBean(targetBeanName);
                if (containsBean) {
                    //移除bean的定义和实例
                    defaultListableBeanFactory.removeBeanDefinition(targetBeanName);
                }
                //注册新的bean定义和实例
                defaultListableBeanFactory.registerBeanDefinition(targetBeanName, BeanDefinitionBuilder.genericBeanDefinition(Test55.class).getBeanDefinition());
                bean = null;
                return new Test55();
            }
            return bean;
        }
     
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            return bean;
        }
    }

    在事件监听器中,分别注入和获取Bean对象。分别打印两个对象的类名,可以看到两个类名都是com.example.hellododo.data.Test55

    @Slf4j
    @Component
    public class LocalEventListener implements ApplicationListener<ContextRefreshedEvent> {
     
        @Autowired
        private DefaultListableBeanFactory defaultListableBeanFactory;
     
        private boolean doFist = true;
     
        @Autowired
        private Test22 test22;
     
        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
     
            Test22 bean = defaultListableBeanFactory.getBean(Test22.class);
            if (doFist) {
                if (bean != null) {
                    log.info("autowiredClassName={}, beanClassName={}", test22.getClass().getName(), bean.getClass().getName());
                    doFist = false;
                }
            }
        }
    }
    正因为当初对未来做了太多的憧憬,所以对现在的自己尤其失望。生命中曾经有过的所有灿烂,终究都需要用寂寞来偿还。
  • 相关阅读:
    将springboot安装成windows服务启动。
    jackson将json数组转成List、普通数组。
    maven编译正常,运行报错:中没有主清单属性
    [SQL]SUTFF内置函数的用法 (删除指定长度的字符并在指定的起始点插入另一组字符)
    sql语句中charindex的用法 可用于截取字符串
    C# 中怎么将string转换成int型
    C#判断奇偶数的函數
    asp.net 下载Excel (数据流,不保存)--客户端
    C# DateTime 日期加1天 减一天 加一月 减一月 等方法(转)
    ASP.NET jquery ajax传递参数
  • 原文地址:https://www.cnblogs.com/candlia/p/11919922.html
Copyright © 2020-2023  润新知