关注Java架构栈,干货天天都不断哦!

大家好,我是千锋文哥。上期文章,我给大家介绍了 IOC容器的依赖注入。到目前为止,大家应该对如何创建Bean,并给创建的Bean进行赋值有了清晰的认识。现在是时候讨论 Bean从产生到销毁整个过程的细节了,也就是 Bean的生命周期。在这里文哥先温馨提示: Bean的生命周期是面试高频点之一,希望大家好好掌握哦~

一. Bean生命周期的概述

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

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

判断对象是否为空的工具类_判断对象是否为空对象_判断对象是否为null

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

二. 详解 Bean的生命周期

1.

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

判断对象是否为空对象_判断对象是否为空的工具类_判断对象是否为null

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

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

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

1.2 第二步:在的配置文件中管理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">

1.3 第三步:编写测试类

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

1.4 第四步:打断点测试

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

判断对象是否为null_判断对象是否为空的工具类_判断对象是否为空对象

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

2.

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

判断对象是否为空对象_判断对象是否为空的工具类_判断对象是否为null

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

3.

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

判断对象是否为空的工具类_判断对象是否为null_判断对象是否为空对象

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

4.

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

判断对象是否为空对象_判断对象是否为null_判断对象是否为空的工具类

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

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

5.

这也是一个接口。

判断对象是否为空对象_判断对象是否为空的工具类_判断对象是否为null

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

6.

判断对象是否为空的工具类_判断对象是否为空对象_判断对象是否为null

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

7.

判断对象是否为null_判断对象是否为空的工具类_判断对象是否为空对象

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

8. are

判断对象是否为空的工具类_判断对象是否为空对象_判断对象是否为null

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

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

public class User implements ApplicationContextAware {
    private ApplicationContext context;

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

}

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

判断对象是否为null_判断对象是否为空的工具类_判断对象是否为空对象

我们看看这个是什么:

判断对象是否为空的工具类_判断对象是否为空对象_判断对象是否为null

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

9.

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

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

10.

该接口我们也叫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;
    }
}

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

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

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

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那么在后续初始化方法因为通过()方法获取不到Bean实例对象会报空指针异常,因为后置处理器从 IoC容器中取出Bean实例对象没有再次放回IoC容器中。

10.2 第二步:定义一个Pojo类

我们定义一个类,在里面定义一个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 第三步:在的配置文件中配置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">

    
    <bean id="student" class="com.qf.bean.Student" init-method="init">
        <property name="id" value="10010">
        <property name="name" value="eric">
        <property name="age" value="12">
    
    
    
    <bean class="com.qf.processor.CustomizeBeanProcessor">
</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);
    }
}

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

判断对象是否为null_判断对象是否为空的工具类_判断对象是否为空对象

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

11.

判断对象是否为空对象_判断对象是否为空的工具类_判断对象是否为null

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

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

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

判断对象是否为空对象_判断对象是否为null_判断对象是否为空的工具类

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

判断对象是否为null_判断对象是否为空的工具类_判断对象是否为空对象

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

三. 验证 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. 定义的配置文件

定义的配置文件,管理我们的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">

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

    
    <bean class="com.qf.processor.CustomizeBeanProcessor">

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();
    }
}

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

判断对象是否为空对象_判断对象是否为空的工具类_判断对象是否为null

四. 总结

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

- 千锋教育 -

近期课程上新:

Java基础|基础|Java高级框架|微服务架构|TiDB分布式数据库入门最佳实践|VUE全面剖析及前后端联动实战|23种设计模式|企业级项目实战|Java面试宝典


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注