zoukankan      html  css  js  c++  java
  • 面试官:小伙子,你说一下java 对象创建和 Spring Bean 的生命周期吧

    理解对象和Bean的关系

    java 是一种面向对象的语言,简而言之,一切皆对象。Bean自然也是对象,只不过它是托管给 Bean 工厂管理着的对象。

    java 对象如何被创建

    在写代码时,我们通常用下面的语句来创建一个对象:

    	A a=new A();
    
    

    那么在创建对象的过程中,究竟发生了什么呢。其实上面简单的一句话,在程序中发生了很多很多的事情。
    首先,一个对象是需要内存去存放的。所以会有一个分配内存的过程。分配了内存之后,jvm便会开始创建对象,并将它赋值给 a 变量。然后再去初始化A中的一些属性,并执行A的构造方法。在初始化的过程中,会先执行 static 代码块,再执行构造方法。除此之外,如果有父类,会优先父类的进行执行。大致如下图(图一)所示。

    如何验证对象初始化的过程呢?用下面一段代码验证。这段代码很简单,有静态变量的初始化,有构造方法,有继承。

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    public class InitTest {
        private static final Logger logger = LoggerFactory.getLogger(InitTest.class);
        // 1.静态变量初始化
        static String staticWord = "hello";
        // 2.静态代码块
        static  {
            logger.info("staticWord = "+staticWord);
        }
        public InitTest(){
            logger.info("father construct method invoke...");
        }
        public static void main(String[] args) {
            new Son();
        }
        static class Son extends InitTest{
            static  {
                logger.info("son staticWord init in static");
            }
            public Son(){
                logger.info("son construct method invoke...");
            }
        }
    }
    
    

    运行打印的日志如下,通过分析日志,我们可以得出,静态代码块先于构造方法,父类先于子类的执行顺序。

    00:55:18.869 [main] INFO com.fc.study.InitTest - staticWord = hello
    00:55:18.877 [main] INFO com.fc.study.InitTest - son staticWord init in static
    00:55:18.877 [main] INFO com.fc.study.InitTest - father construct method invoke...
    00:55:18.877 [main] INFO com.fc.study.InitTest - son construct method invoke...
    
    

    Spring Bean 的生命周期

    好的,有了对象的初始化顺序,我们就可以继续分析 bean 的生命周期了。我们可以先回忆一下自己平时是怎么定义一个 bean的。

    @Component
    public class TestBean{
    }
    
    
    @Bean
    public Object myObject(){
    }
    
    

    常用的是上面这两种:第一种是通过Component注解标注类;第二中方式是在方法上做@Bean的注解。我们都知道,注解标注的方法或者类,便会被spring扫描,并最终生成一个bean。本文不详细讨论bean扫描的过程,只分析bean初始化过程中的一些接口。
    那么,Spring 创建 Bean 就可以分为两大步骤,第一步是由Springboot 扫描并获取BeanDefinition;第二部,是初始化Bean。spring 在bean的初始化过程为我们提供了很多的接口,我们可以用它们在bean的生成过程中做一些事情。这些接口均采用回调的方式,以下是部分接口的介绍和回调时机。

    接口 说明 回调时机
    BeanNameAware 如果你的bean实现了该接口的 setName 方法,则可以通过这个方法获取到bean名 发生在bean生命周期初期,早于构造方法
    ApplicationContextAware 如果一个bean实现了该接口的setApplicationContext 方法,则可以通过此方法获取到ApplicationContext 调用于生命周期初期,在BeanNameAware和构造方法之间
    InitializingBean 此接口的方法为 afterPropertiesSet 在bean工厂设置完bean的所有属性之后,会回调此方法。回调时机在构造方法之后
    BeanPostProcessor 此接口有 postProcessBeforeInitialization、postProcessAfterInitialization两个方法,分别对应了Bean生命周期的两个回调 这两个方法也在构造方法之后,不过分别在 InitializingBean 前后

    如果将上面的接口加入,则 bean 生命周期大致如下图(图二):

    同样,我们用代码来验证一下这个回调顺序。用来测试的Bean代码如下,这个测试 bean 没有继承其他父类,仅用来验证springboot的接口在bean生命周期的调用时机:

    package com.fc.study.beanLife;
    
    import com.fc.study.InitTest;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.BeanNameAware;
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.beans.factory.config.BeanPostProcessor;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.stereotype.Component;
    
    @Component
    public class TestBean implements BeanNameAware, InitializingBean, ApplicationContextAware {
        private static final Logger logger = LoggerFactory.getLogger(InitTest.class);
        private String beanName;
        static String staticWord;
        static  {
            logger.info("father staticWord init in static");
            staticWord="hi";
        }
        public TestBean(){
            logger.info("testBean construct method invoke...");
        }
    
        public void setBeanName(String name) {
            logger.info("setBeanName");
            this.beanName = name;
        }
    
        public void afterPropertiesSet() throws Exception {
            logger.info("afterProperties Set");
        }
    
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            logger.info("applictionContextAware");
        }
    }
    
    

    同时,我定义了一个BeanPostProcessor 如下:

    package com.fc.study.beanLife;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanPostProcessor;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.core.Ordered;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    @Component
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public class DefaultBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
    
        private static Logger logger = LoggerFactory.getLogger(DefaultBeanPostProcessor.class);
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            if(beanName.equals("testBean")) {
                logger.info(beanName + " postProcessBeforeInitialization 执行");
            }
            return bean;
        }
    
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if(beanName.equals("testBean")) {
                logger.info(beanName + " postProcessAfterInitialization 执行");
            }
            return bean;
        }
    
        private ApplicationContext applicationContext;
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }
    }
    
    

    接下来是启动类:

    package com.fc.study.beanLife;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import org.springframework.context.annotation.ComponentScan;
    
    @ComponentScan("com.fc.study")
    public class SimpleSpringBoot {
    
        private static final Logger logger = LoggerFactory.getLogger(SimpleSpringBoot.class);
    
        public static void main(String[] args) {
            AnnotationConfigApplicationContext context =
                    new AnnotationConfigApplicationContext(SimpleSpringBoot.class);
    
            logger.info("before get Bean");
            context.getBean(TestBean.class);
            logger.info("after get bean");
        }
    }
    
    

    运行启动类,打印出日志如下:

    2021-01-23 02:18:09,764  INFO InitTest:29 - father staticWord init in static
    2021-01-23 02:18:09,768  INFO InitTest:34 - testBean construct method invoke...
    2021-01-23 02:18:09,768  INFO InitTest:38 - setBeanName
    2021-01-23 02:18:09,768  INFO InitTest:48 - applictionContextAware
    2021-01-23 02:18:09,768  INFO DefaultBeanPostProcessor:27 - testBean postProcessBeforeInitialization 执行
    2021-01-23 02:18:09,768  INFO InitTest:44 - afterProperties Set
    2021-01-23 02:18:09,768  INFO DefaultBeanPostProcessor:34 - testBean postProcessAfterInitialization 执行
    2021-01-23 02:18:11,449  INFO SimpleSpringBoot:24 - before get Bean
    2021-01-23 02:18:11,449  INFO SimpleSpringBoot:26 - after get bean
    
    

    看看这个日志,印证了图二对各个接口调用时机结论。

    总结

    对象初始化,就是创建对象,并且初始化其属性的过程。首先是加载类文件,其次对象所需要的内存。然后静态代码块会被调用,最后是构造方法。
    Spring Bean的初始化,除了创建对象这些步骤之外,还在其中穿插了一些生命周期的接口。首先在类加载完成后,会得到BeanDefinition,然后通过这个定义来初始化,而不是直接通过加载后的类对象来生成对象。在静态代码块和构造方法中间,Spring提供了几个Aware接口,如表格中的BeanNameAware和ApplicationContextAware。在构造方法调用结束,并且springboot给bean set了所有属性之后,会调用Initializing接口和BeanPostProcessor。
    以上,便是我理解的 spring bean 生命周期,它就是 spring 在帮我们初始化对象管理对象的过程中额外做了一些事情。

  • 相关阅读:
    快速幂模板
    部分有关素数的题
    POJ 3624 Charm Bracelet (01背包)
    51Nod 1085 背包问题 (01背包)
    POJ 1789 Truck History (Kruskal 最小生成树)
    HDU 1996 汉诺塔VI
    HDU 2511 汉诺塔X
    HDU 2175 汉诺塔IX (递推)
    HDU 2077 汉诺塔IV (递推)
    HDU 2064 汉诺塔III (递推)
  • 原文地址:https://www.cnblogs.com/lwh1019/p/14320881.html
Copyright © 2011-2022 走看看