前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Bean生命周期的标准答案

Spring Bean生命周期的标准答案

作者头像
凯哥的Java技术活
发布2022-07-08 14:10:46
2060
发布2022-07-08 14:10:46
举报

In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans。

什么是bean?在Spring中,被Spring ioc容器管理的实例,称作为bean。那么bean的声明周期是怎样的呢?本文带你认识Spring bean声明周期的标准答案。

生命周期

直接说答案太枯燥了,我们来做个比喻。

假如你现在进了饭馆,你想吃的菜厨师不会做,你这时候就得教他怎么做这道菜!那么流程是什么呢?

给后厨看菜谱--->后厨研究一下--->大厨们根据自己经验修改一下--->开始炒菜--->放佐料--->放其他佐料--->放盐--->放其他佐料--->出锅--->消灭掉

类似一下就是spring bean的生命周期:

定义--->解析--->修改定义--->实例化--->属性赋值--->初始化前置增强--->初始化--->初始化后置增强--->使用--->销毁

Bean定义

做菜第一步,告诉厨师怎么做!

菜谱:BeanDefinition,Bean定义,最常见方式有两种,xml方式和java注解方式。

Bean定义就是声明Spring如何进行创造对象的,如果Spring是厨师,那么Bean定义就是菜单。

一个bean定义有以下9种属性:

  1. Class :定义如何bean的class。
  2. Name:定义bean的名称
  3. Scope:定义bean的作用域
  4. Constructer Arguments:定义使用的构造方法
  5. Properties:属性
  6. Autowired Mode:注入模型
  7. Lazy initialization mode:懒加载
  8. Initialization method:初始化方法
  9. Destruction method:销毁方法

下面着重讲一下几个关键的bean定义属性

Class

定义一个bean实例化成为哪个类。可能会有读者觉得,我平常就是写@Service @Controller @Component,bean的class就是写死的,难道还会故意把bean定义成为其他的class吗?

我们先来看一个有趣的特性。如下代码演示:

这里我们先定义Deer,也就是鹿

代码语言:javascript
复制
/**
 * This is deer.
 */
public class Deer {

  public Deer(){
    System.out.println("Dear constructs");
  }
}

然后定义Horse类,马

代码语言:javascript
复制
/**
 * This is horse.
 */
public class Horse {

  public Horse(){
    System.out.println("Horse constructs");
  }

}

下面代码可以先不用管:

代码语言:javascript
复制
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    GenericBeanDefinition definitionClass = (GenericBeanDefinition) beanFactory.getBeanDefinition("deer");
    ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
    definitionClass.setBeanClass(Horse.class);
  }
}

然后这是我们的测试代码,以下代码是构造一个spring容器,并注册一个Deer类的bean,然后把上面的那个MyBeanFactoryPostProcessor注册进去。

代码语言:javascript
复制
public class TestApp {

  public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(Deer.class);
    context.register(MyBeanFactoryPostProcessor.class);
    context.refresh();

    System.out.println(context.getBean("deer").getClass());
  }
}

我们预期应该是添加的Deer类的bean,获取到的bean的class类应该是deer。

看下实际结果。运行程序后,打印日志如下:

代码语言:javascript
复制
class com.example.spring.bean.lifecycle.beanclass.Horse

竟然打印出的是Horse,这不是指鹿为马吗?!

再联想一下,mybatis为什么只写Mapper接口就能进行dao操作?我们知道接口是无法实例化的。

没错,mybatis就是在这点进行了扩展,通过修改mapper的bean定义中的class属性,把mapper的class修改成了一个FactoryBean,从而实现了mapper的代理类。

这个现象比较有趣,这里其实我们主要使用了BeanFactoryPostProcessor去修改了Bean定义,即使注册的bean是Deer,但是我们修改成了Horse。关于BeanFactoryPostProcessor的内容请往后看。

这就是bean定义的class属性。

Autowired Mode(注入模型)

注入模型分为四种,分别是

no:不注入,这也是默认的选项。意味着如果我们不在某个字段上标记@Autowired等标签,spring就把该字段当做是普通字段,不会进行自动注入。

byName:在容器中寻找和属性同名的bean进行注入。

byType:在容器中寻找字段的class的bean并注入。

constructor:和byType相似,只不过是在构造函数上注入。

可能说的太抽象,我们来举个例子说明下:

我们首先定义一个最最简单的Class,稍后我们把它加到spring容器中。

代码语言:javascript
复制
public class Blue {
}

然后我们声明另一个类,Green,Green里面有一个Blue属性。

代码语言:javascript
复制
public class Green {

  private Blue blue;

  public Green(){
    System.out.println("Constructs with no arg");
  }

  public Green(Blue blue){
    System.out.println("Constructs with a blue arg");
    this.blue = blue;
  }

  public Blue getBlue() {
    return blue;
  }

  public void setBlue(Blue blue) {
    this.blue = blue;
  }
}

默认情况下,我们将这两个类添加到spring容器中,Green的bean中是不会注入Blue的,因为我们没有添加@Autowired。

代码语言:javascript
复制
public class App {

  public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(Green.class);
    context.register(Blue.class);
// context.register(AutowiringModeChangeBeanFactoryPostProcessor.class);
    context.refresh();
    Green bean = context.getBean(Green.class);
    System.out.println(bean.getBlue());
  }

}

运行程序,打印输出

符合我们预期。

然后我们借助实现上一个例子介绍的BeanFactoryPostProcessor,来对Green的bean定义进行一些修改,将它的注入模型改为byType。

代码语言:javascript
复制
public class AutowiringModeChangeBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanFactory.getBeanDefinition("green");
    beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
  }
}

然后我们再来测试一下。

代码语言:javascript
复制
public class App {

  public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(Green.class);
    context.register(Blue.class);
    context.register(AutowiringModeChangeBeanFactoryPostProcessor.class);
    context.refresh();
    Green bean = context.getBean(Green.class);
    System.out.println(bean.getBlue());
  }

}

输出结果变成了

发现Green的bean中的blue字段不为空。

之所以会实现这个效果,其实就是我们讲的,我们把Green的这个bean的注入模型改成了byType,spring就在容器中寻找blue的bean,然后注入到属性中。所以Green 中的blue不为空。

Bean扫描

以上spring的bean定义,对应了我们说的菜谱。那么下一步,厨师要理解你的菜谱了。

这一步我们成为扫描,或者解析bean定义。

什么是解析?就是扫描我们的bean定义然后添加到spring容器中。

这里我先把答案放出来。我们的bean定义最后都会放到BeanFactory的beanDefinitionMap中去。

然后我们打断点进去看一看是怎么添加进去的

查看调用栈,一步步进行添加到BeanDefinitionMap中去。xml解析同样的原理,就是把xml配置解析成bean定义。如果有需求,甚至可以自己定义txt解析去添加bean定义。解析不展开描述。

BeanFactoryPostProcessor

接下来,厨师可以稍微修改下你的菜谱。

在我们第一节,讲bean定义的时候,我们提到过BeanFactoryPostProcessor。

在spring容器启动时,加载我们的bean定义后,来到spring提供的第一个扩展点!那就是BeanFactoryPostProcessor。

BeanFactoryPostProcessor允许我们读取bean的配置信息,然后在spring初始化bean之前去修改bean定义。

只需要实现该接口,并添加到容器中,spring就会执行实现的方法,进行你需要的增强。

BeanFactoryPostProcessor只有一个方法需要实现 postProcessBeanFactory,在这个方法中我们可以根据传递过来的beanfactory来获取想要修改的bean定义。正如我们之前修改的注入模型和class时候做的。

实例化

接下来开始炒菜!

实例化意味着执行构造方法。

在refresh方法的finishBeanFactoryInitialization方法中,会对所有的单例非懒加载的bean进行创建,实例化便是其中步骤之一。

首先会先去获取bean,不存在就创建bean,解决循环依赖等。这里我们不展开描述,这不影响我们今天的专题。

如果你不知道bean是在哪个方法实例化的,那么你就打断点在构造方法上,毕竟只要实例化,无论是反射也好new也罢,都会走构造方法。

我们代码如下:

代码语言:javascript
复制
public class Green {

    private Blue blue;

    public Green(){
        System.out.println("Constructor with no arg invoked");
    }

    public Blue getBlue() {
        return blue;
    }

    public void setBlue(Blue blue) {
        this.blue = blue;
    }
}

跟踪断点:

调用栈信息如下:

也就是说Green的构造是在

代码语言:javascript
复制
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

这个方法中执行的。

注意实例化是初始化的区别,实例化仅仅是调用构造方法进行对象的创建,不包含属性的赋值等初始化方法。

属性赋值

属性赋值,我类比为放佐料。

在上一步对象构造完之后,对象内的属性还没有进行赋值。

毫无疑问,下一步就是赋值操作,例如讲Blue注入到Green里面去。

该方法就在creatBeanInstance方法的下面。

经过该步骤后,green的blue就赋值成功了,可以根据断点看。

具体的populate步骤,有兴趣可以自己跟下源码,我这里把获取green的那行代码放出来,小伙伴们可以自己打断点进去看看。

在org.springframework.beans.factory.support.DefaultListableBeanFactory#

doResolveDependency 方法中解析依赖,然后进入到下图的断点中:

值得注意的是我们之前测试注入模型的时候,设置的注入类型byType或者byName也是在populateBean里面处理的。代码如下:

看不懂没关系,这些个小细节先省略。我们知道这一步是进行属性赋值就行!

下面的步骤该是什么呢?属性也赋值完毕了,还要干什么?

初始化

这里我把初始化前置增强,初始化,初始化后置增强,统一归为初始化。

类似炒菜的比喻,就是放其他佐料,放盐,放其他佐料。

自定义初始化的操作,这里我们还可以进行扩展,通过实现BeanPostProcessor接口。

利用BeanPostProcessor可以自定义逻辑应用于修改bean。换句话说,也就是在bean实例化,赋值操作完毕后,允许我们通过这个接口在调用init方法之前和之后进行一些操作。

当然init方法我们也是可以定义!可以通过实现InitializingBean接口,或者在初始化方法上添加@PostConstruct注解。

BeanPostProcessor有两个方法

代码语言:javascript
复制
postProcessBeforeInitialization()

postProcessAfterInitialization()

见名知意,分别是在init之前和之后的方法。

执行的源码如下:

这不就是初始化前置增强(放其他佐料),初始化(放盐),初始化后置增强(放其他佐料)!

接下来我们来验证一下。

首先我们先实现一个BeanPostProcessor,什么操作都不做,只是打印一下信息。

代码语言:javascript
复制
public class MyBeanPostProcessor implements BeanPostProcessor {

  @Override
  public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    System.out.println("before initialization");
    return bean;
  }

  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    System.out.println("after initialization");
    return bean;
  }
}

然后实现InitializingBean 接口或者使用@PostConstruct注解。

代码语言:javascript
复制
public class Green {

  private Blue blue;

  @PostConstruct
  public void init(){
    System.out.println("post construct");
  }

  public Green(){
    System.out.println("Constructs with no arg");
  }

  public Green(Blue blue){
    System.out.println("Constructs with a blue arg");
    this.blue = blue;
  }

  public Blue getBlue() {
    return blue;
  }

  public void setBlue(Blue blue) {
    this.blue = blue;
  }
}

可以进行断点进行查看执行时机,篇幅原因,我这里只打印结果,感兴趣小伙伴自己断点调试效果更佳。

结果如下:

证实了我们关于初始化的理论,before,init,after。

也就是执行

  1. BeanPostProcessor的postProcessBeforeInitialization()方法
  2. 然后执行自定义的@PostConstruct方法
  3. 然后执行postProcessAfterInitialization()方法

至此,bean的创建就结束了。

至于像其他什么ApplicationContextAware,BeanNameAware,没什么特别的意义,有兴趣自己打断点看一下就行了。

剩下就是在容器运行期间,进行bean的一些使用了。

销毁我不准备讲,不是重头戏!可以自己测试@PreDestroy注解,相同的套路,打断点调试!

最后我们来系统走一遍:

  1. bean定义
  2. bean定义被spring解析
  3. post process bean factory
  4. Constructs with no arg
  5. before initialization
  6. post construct
  7. after initialization
  8. pre destory

最后把代码贴出来,很简单,自己断点调试,看下控制台输出,会理解的更深!

代码语言:javascript
复制
public class Green {

  @PostConstruct
  public void init(){
    System.out.println("post construct");
  }

  @PreDestroy
  public void destroy(){
    System.out.println("pre destory");
  }

  public Green(){
    System.out.println("Constructs with no arg");
  }
}
代码语言:javascript
复制
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    System.out.println("post process bean factory");
  }
}
代码语言:javascript
复制
public class MyBeanPostProcessor implements BeanPostProcessor {

  @Override
  public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    System.out.println("before initialization");
    return bean;
  }

  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    System.out.println("after initialization");
    return bean;
  }
}
代码语言:javascript
复制
public class App {

  public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(Green.class);
    context.register(MyBeanFactoryPostProcessor.class);
    context.register(MyBeanPostProcessor.class);
    context.refresh();
    context.registerShutdownHook();
  }

}

总结

本文针对spring bean生命周期专题,进行从bean定义到bean销毁流程的整体讲解。推荐结合spring的官方文档和代码断点调试,来彻底理解整个生命周期,以及我们能对其做怎样的扩展。希望这篇文档能对你有一点点帮助。

END

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-06-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 凯哥的Java技术活 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档