面试官必问--谈谈Spring Bean对象的生命周期吧

现在是时候讨论Spring Bean从产生到销毁整个过程的细节了,也就是Spring Bean的生命周期。在这里文哥先温馨提示:Spring Bean的生命周期是面试高频点之一,希望大家好好掌握哦~

一. Spring Bean生命周期的概述

如果没有Spring的环境,Java Bean的生命周期非常简单,通过new关键字创建的对象就可以被使用,一旦这个对象不再被使用了(JVM中通过可达性搜索算法判断对象是否可用),这个对象就会被判定为垃圾对象,然后被垃圾回收器回收。

但是在Spring中,Bean的生命周期就不是这么简单的了。由于Spring对Bean管理灵活度非常高,这就导致Spring Bean的生命周期非常复杂。接下来,文哥带领大家一探Spring Bean生命周期的细节。为了完整地展示Spring的生命周期,文哥用一幅图来描述Spring Bean生命周期的整个过程。

我们发现整个生命周期异常复杂,别慌,文哥这就给大家慢慢地分析,跟我来。

二. 详解Spring Bean的生命周期

1. BeanNameAware

这是一个接口,在Spring源码中是这样描述的。如果Spring中的Bean实现了这个接口的话,Spring的Bean的id将会传入给setBeanName的形参里面。

现在我们验证一下是不是如我们上面定义的那样,接下来我们通过几个步骤来进行分析。

1.1 第一步:定义一个类,该类必须实现BeanNameAware接口

public class User implements BeanNameAware 
{    
  public void setBeanName(String name) 
    {        
      System.out.println("bean的名称是:" + name);    
    } 
}

1.2 第二步:在Spring的配置文件中管理Bean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="user" class="com.qf.bean.User"></bean>
</beans>

1.3 第三步:编写测试类

public class TestUser {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    }
}

1.4 第四步:打断点测试

我们在User的setBeanName上打上断点,查看一下效果:

以上结果就验证了刚刚说的,这个方法里面会接收Bean的id的值。

2. BeanClassLoaderAware

在源码中,会将其描述成一个接口。

这个接口里面定义了setBeanClassLoader方法。这个方法就是设置Bean加载器的方法,方法的形式参数传递的是一个类加载器对象。

3. BeanFactoryAware

Spring也是将其描述成了一个接口。

如果一个Bean实现了这个接口,可以获取这个Bean的Bean工厂。

4. EnvironmentAware

如果一个Bean实现了这个接口,就可以获取当前Bean对应的运行环境。

如果我们想获取Bean对应的环境信息,我们可以这么做:

public class User implements  EnvironmentAware{
    public void setEnvironment(Environment environment) {
        System.out.println(environment);
    }
}

5. ResourceLoaderAware

这也是一个接口。

如果一个Bean实现了这个接口我们可以获得一个资源加载器,用来去加载Bean所需要用到的资源信息。

6. ApplicationEventPublisherAware

这个接口是用来做事件发布用的。Spring设计这个组件主要是用来组件解耦使用的,如果一个Bean所属的类实现了这个接口,就可以获取一个事件发布器。

7. MessageSourceAware

Spring同样将其设计成了一个接口,我们可以通过这个接口去获取Bean相关的国际化资源信息。

8. ApplicationContextAware

当一个Bean所属的类实现了这个接口之后,这个类就可以方便地获得ApplicationContext对象(Spring上下文),Spring发现某个Bean实现了ApplicationContextAware接口,Spring容器会在创建该Bean之后,自动调用该Bean的setApplicationContext(参数)方法,调用该方法时,会将容器本身ApplicationContext对象作为参数传递给该方法。

我们在Bean所属的类上面实现这个接口:

public class User implements ApplicationContextAware {
    private ApplicationContext context;

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }

}

我们打断点验证我们的猜想:

我们看看这个context是什么:

这不就是我们上面说的,将ApplicationContext实例对象传入进来了吗?

9. ServletContextAware

在上面我们获取了Spring的Bean工厂实例对象,接下来Spring框架会去判断当前容器是否是一个Web类型的容器实例,如何判断?就需要调用ServletContextAware中的setServletContext方法,获取一个ServletContext容器。那到底是如何获取的呢?

private ServletContext servletContext;
@Override
public void setServletContext(ServletContext servletContext) {
    this.servletContext = servletContext;
}

10. BeanPostProcessor

该接口我们也叫Bean的后置处理器,作用是在Bean对象在实例化和依赖注入完毕后,在显式调用初始化方法的前后添加我们自己自定义的逻辑。注意是Bean实例化完毕后及依赖注入完成后触发的。接口的源码如下

public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

这两个方法是什么意思呢,给大家详细介绍:

  • postProcessBeforeInitialization:Bean实例化、依赖注入完毕之后,在调用Bean初始化之前完成一些定制的初始化任务

  • postProcessAfterInitialization:Bean实例化、依赖注入、初始化完毕时执行

现在,我们就自定义一个Bean的后置处理器,有以下几个实现步骤。

10.1 第一步:创建一个类,实现BeanPostProcessor接口


public class CustomizeBeanProcessor implements BeanPostProcessor {

    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("执行before方法,bean是:" + bean + "bean的名称是:" + beanName);
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("执行after方法,bean是:" + bean + "bean的名称是:" + beanName);
        return bean;
    }
}

文哥在这里特别提示:这两个方法的返回值千万不能返回为null。如果返回null那么在后续初始化方法因为通过getBean()方法获取不到Bean实例对象会报空指针异常,因为后置处理器从Spring IoC容器中取出Bean实例对象没有再次放回IoC容器中。

10.2 第二步:定义一个Pojo类

我们定义一个Student类,在里面定义一个init方法。

public class Student {
    
    private Integer id;
    private String name;
    private Integer age;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    //定义一个初始化bean的时候需要执行的方法
    public void init(){
        System.out.println("初始化方法init执行了.....");
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

10.3 第三步:在Spring的配置文件中配置pojo和Bean的后置处理器

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--管理student-->
    <bean id="student" class="com.qf.bean.Student" init-method="init">
        <property name="id" value="10010"></property>
        <property name="name" value="eric"></property>
        <property name="age" value="12"></property>
    </bean>
    
    <!--管理bean的后置处理器-->
    <bean class="com.qf.processor.CustomizeBeanProcessor"></bean>
</beans

10.4 第四步:测试

我们定义一个测试类,初始化一个IOC容器,然后从容器中获取Bean,然后查看控制台效果。

public class TestStudent {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = (Student) context.getBean("student");
        System.out.println(student);
    }
}

我们查看控制台输出信息:

通过验证,我们终于知道了Bean的后置处理器的作用了。

11. InitializingBean

该接口只有这一个接口。这个方法将在所有的属性被初始化后调用,但是会在 init 前调用。这个接口我们一般在工作中用于工厂+策略模式优化过多的if else代码块。

现在文哥给大家演示一下,这个接口的简单用法:

11.1 第一步:在pojo类上实现这个接口。

11.2 第二步:运行测试代码,查看控制台效果

到这里我们基本上给大家把Spring Bean生命周期中所涉及的主要接口给大家讲解清楚了。接下来文哥再给大家用代码演示Spring Bean的生命周期。

三. 验证Spring Bean的生命周期

1. 定义一个POJO类

定义一个POJO类,我们分别实现上面刚刚给大家介绍的生命周期中的接口。

public class Student implements BeanNameAware, BeanFactoryAware, ApplicationContextAware,InitializingBean, DisposableBean {

    private Integer id;
    private String name;
    private Integer age;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    //定义一个初始化bean的时候需要执行的方法
    public void init(){
        System.out.println("初始化方法init执行了.....");
    }

    //定义销毁bean的时候需要指定的方法
    public void preDestroy(){
        System.out.println("销毁bean的方法preDestroy执行了....");
    }

    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("Student的setBeanFactory方法执行了.....");
    }

    public void setBeanName(String beanName) {
        System.out.println("Student的bean的名称是:" + beanName);
    }

    public void destroy() throws Exception {
        System.out.println("destroy方法执行了....");
    }

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet被调用了....");
    }
}

2. 定义Bean的后置处理器

这个后置处理器,在上面文哥给大家介绍过,我们需要实现里面的两个方法,注意方法的返回值一定不能为null。


public class CustomizeBeanProcessor implements BeanPostProcessor {

    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("执行before方法,bean是:" + bean + "bean的名称是:" + beanName);
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("执行after方法,bean是:" + bean + "bean的名称是:" + beanName);
        return bean;
    }
}

3. 定义Spring的配置文件

定义Spring的配置文件,管理我们的POJO类和后置处理器类。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--管理student-->
    <bean id="student" class="com.qf.bean.Student" init-method="init" destroy-method="preDestroy">
        <property name="id" value="10010"></property>
        <property name="name" value="eric"></property>
        <property name="age" value="12"></property>
    </bean>

    <!--管理bean的后置处理器-->
    <bean class="com.qf.processor.CustomizeBeanProcessor"></bean>
</beans>

4. 定义测试类,查看控制台输出效果


public class TestStudent {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = (Student) context.getBean("student");
        System.out.println(student);
        //关闭容器,销毁Bean
        ((ClassPathXmlApplicationContext) context).close();
    }
}

我们此时来查看一下控制台的效果,如下图所示:

通过这篇文章,给大家非常详细地介绍了Spring中Bean的生命周期,可以说本文是从源码角度,深入浅出地剖析了Spring中Bean的生命周期整个执行流程。虽然本文的讲解步骤比较繁琐,但如果大家在面试中能够回答的这么深入,相信大家一定可以让面试官刮目相看!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值