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 在帮我们初始化对象管理对象的过程中额外做了一些事情。

  • 相关阅读:
    ARM Linux 3.x的设备树(Device Tree)
    ubuntu 14.04 编译内核出现unable to locate package ncurses-devel 问题的解决
    Device Tree Usage( DTS文件语法)
    Ubuntu 14.04中gedit打开文件出现中文乱码问题
    Jenkins中集成jmeter-maven插件
    Linux(centos6.5)下安装jenkins
    IM系统架构设计之浅见
    一些常用软件的网络端口协议分类介绍
    Jenkins执行批处理文件失败
    八大持续集成工具
  • 原文地址:https://www.cnblogs.com/lwh1019/p/14320881.html
Copyright © 2011-2022 走看看