与 Aware 那篇文章的引言差不多,大家如果了解过 Spring Bean 的生命周期,其实就能发现提到了 InitializingBean 等一些初始化的接口。这一篇,我们也是对 Bean 的初始化和销毁的几种方式进行一定的了解。
首先,大家要是知道,在一个 bean 被正式的使用前后,会进行初始化和销毁,去做一些操作,例如初始化数据库连接池,或者释放自身的资源等。

早在 JavaWeb 的 Servlet 中,虽然大家常用的还是直接重写 doGet 和 doPost 的方式,但是在刚开始的时候学习 Bean 的生命周期,以及一些需要重写 init 等方法的接口,都会见到初始化和销毁的概念。
正如下面所写,在 Servlet 中 init 方法是被 Tomcat 执行的,也就是说,虽然生命周期是定义在 Serlvet 中的,但是真正执行调用它的却是第三方,这里也能体现出一种回调的概念。

生前——出生——服务——死亡——埋葬

  1. 生前:当 Tomcat 第一次访问 Servlet,Tomcat 会创建 Servlet 的实例
  2. 出生:Tomcat 会调用 init() 方法初始化这个对象
  3. 服务: 客户端访问 Servlet 的时候,service() 方法会被调用
  4. 死亡: 当 Tomcat 被关闭或者Servlet长时间不被使用,destroy() 方法会被调用
  5. 埋葬: destroy() 方法被调用后,Servlet 就等待垃圾回收(不轻易),有需要则用 init() 方法重新初始化

1. init-method 和 destroy-method

我们首先分别创建 Student 和 Teacher 两个实体类,用于测试

  1. public class Student {
  2. private String name;
  3. public Student() {
  4. System.out.println("Student 的构造函数执行了");
  5. }
  6. public void setName(String name) {
  7. System.out.println("Student 的 set 方法执行了");
  8. this.name = name;
  9. }
  10. public void init() {
  11. System.out.println("Student:" + name + " 被初始化了。。。");
  12. }
  13. public void destroy() {
  14. System.out.println("Student:" + name + " 被销毁了。。。");
  15. }
  16. }
  17. public class Teacher {
  18. private String name;
  19. public Teacher() {
  20. System.out.println("Teacher 的构造函数执行了");
  21. }
  22. public void setName(String name) {
  23. System.out.println("Teacher 的 set 方法执行了");
  24. this.name = name;
  25. }
  26. public void init() {
  27. System.out.println("Student:" + name + " 被初始化了。。。");
  28. }
  29. public void destroy() {
  30. System.out.println("Student:" + name + " 被销毁了。。。");
  31. }
  32. }

Student 通过 xml 的方式注入,通过两个属性指定哪个方法为初始化,哪个为销毁

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <bean class="com.ideal.init_destroy.Student" init-method="init" destroy-method="destroy">
  7. <property name="name" value="张三"/>
  8. </bean>
  9. </beans>

Teacher 通过注解的方式注入,利用 @Bean 注解的属性进行指定

@Configuration
public class InitDestroyConfiguration {
    @Bean(initMethod = "init", destroyMethod = "destroy")
    public Teacher getTeacherBean() {
        Teacher teacher = new Teacher();
        teacher.setName("李四");
        return teacher;
    }
}

测试代码,注意这里没有使用 ApplicationContext 来接,是因为我们想主动的去调用 close 方法,具体为什么 ApplicationContext 中没有 close,而其子类有,放到后面的文章再讲解。

public class Test {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        context.close();

        AnnotationConfigApplicationContext context2 = new AnnotationConfigApplicationContext(InitDestroyConfiguration.class);
        context2.close();
    }
}

执行效果,大家可以显式的看到执行的顺序了。


Student 的构造函数执行了
Student 的 set 方法执行了
Student:张三 被初始化了。。。
Student:张三 被销毁了。。。

Teacher 的构造函数执行了
Teacher 的 set 方法执行了
Teacher:李四 被初始化了。。。
Teacher:李四 被销毁了。。。

在创建 Bean 的时候执行顺序的优先级就是:构造函数、set 方法、初始化、销毁。

2. JSR250规范实现初始化和销毁

JSR 是 Java Specification Requests 的缩写,意思是 Java 规范提案。
JSR 250 规范包含用于将资源注入到端点实现类的注释和用于管理应用程序生命周期的注释。

JSR 主要包括三个注解,@Resource 、@PostConstruct 、@PreDestroy
第一个可能用的还多一些,因为我们很多情况下可以选择 @Resource 来替代 @Autowired,这个我们有机会单独说。
今天主要讲解的是后面两个,就通过名字也可看出来,@PostConstruct 代表的就是 init 而 PreDestroy 代表的就是 destory。

例如我们修改刚才的实体类,增加两个添加了这两个注解的实体

public class Teacher {

    private String name;

    public Teacher() {
        System.out.println("Teacher 的构造函数执行了");
    }

    public void setName(String name) {
        System.out.println("Teacher 的 set 方法执行了");
        this.name = name;
    }

    public void init() {
        System.out.println("Student:" + name + " 被初始化了");
    }
    public void destroy() {
        System.out.println("Student:" + name + " 被销毁了");
    }

    @PostConstruct
    public void postConstruct() {
        System.out.println("Teacher 的 postConstruct 方法执行了");
    }

    @PreDestroy
    public void postPreDestroy() {
        System.out.println("Teacher 的 postPreDestroy 方法执行了");
    }
}

沿用上面的配置类和测试类(注意这里 Teacher 是通过注解 @Bean 注入的,不是 xml),运行一下:

Teacher 的构造函数执行了
Teacher 的 set 方法执行了
Teacher 的 postConstruct 方法执行了
Teacher:李四 被初始化了
Teacher 的 postPreDestroy 方法执行了
Teacher:李四 被销毁了

大家可以试试如果这个 bean 的 init 和 destroy 是通过 xml 配置的,@PostConstruct 和 @PreDestroy 他们两个和前面的是不可以共存的,只有通过注解 @Bean 配置才可以。

所以可以得出一个结论,在通过 @Bean 配置 init 等的时候,JSR250 的这两个初始化和销毁的注解是可以并存的,而且 JSR250 的优先级更高。

3. InitializingBean 和 DisposableBean

�SpringFramework 内部已经预先定义好了两个关于生命周期的接口,InitializingBean 中的方法 afterPropertiesSet 会在初始化时执行,而 DisposableBean 中的 destroy 方法会在销毁阶段执行。

因为与之前我们通过 @Bean 指定的方法 destroy 冲突了,我们将原来的 init 和 destroy 方法修改为 myInit 和 myDestroy。

public class Teacher implements InitializingBean, DisposableBean {

    private String name;

    public Teacher() {
        System.out.println("Teacher 的构造函数执行了");
    }

    public void setName(String name) {
        System.out.println("Teacher 的 set 方法执行了");
        this.name = name;
    }

    public void myInit() {
        System.out.println("Teacher:" + name + " 被初始化了");
    }
    public void myDestroy() {
        System.out.println("Teacher:" + name + " 被销毁了");
    }


    @PostConstruct
    public void postConstruct() {
        System.out.println("Teacher 的 postConstruct 方法执行了");
    }

    @PreDestroy
    public void postPreDestroy() {
        System.out.println("Teacher 的 postPreDestroy 方法执行了");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("Teacher 的 DisposableBean # destroy 方法执行了");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Teacher 的 InitializingBean # afterPropertiesSet 方法执行了");
    }
}

执行结果

Teacher 的构造函数执行了
Teacher 的 set 方法执行了
Teacher 的 postConstruct 方法执行了
Teacher 的 InitializingBean # afterPropertiesSet 方法执行了
Teacher:李四 被初始化了
Teacher 的 postPreDestroy 方法执行了
Teacher 的 DisposableBean # destroy 方法执行了
Teacher:李四 被销毁了

结论:@PostConstruct → InitializingBean → init-method ,同样销毁也是一样。

4. 原型 bean 的初始化与销毁(多例)

前面都不变,我们只是将默认单例的 Bean 修改为 多例的

@Configuration
public class InitDestroyConfiguration {
    @Bean(initMethod = "myInit", destroyMethod = "myDestroy")
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Teacher getTeacherBean() {
        Teacher teacher = new Teacher();
        teacher.setName("李四");
        return teacher;
    }
}

测试获取 context,即获取 IOC 容器,如下文的运行结果就是什么输出也没有。

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(InitDestroyConfiguration.class);
        context.close();
    }
}

如果真正去获取一下 bean

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(InitDestroyConfiguration.class);
        Teacher teacher = context.getBean(Teacher.class);
        context.getBeanFactory().destroyBean(teacher);
        context.close();
    }
}

运行结果

Teacher 的构造函数执行了
Teacher 的 set 方法执行了
Teacher 的 postConstruct 方法执行了
Teacher 的 InitializingBean # afterPropertiesSet 方法执行了
Teacher:李四 被初始化了
Teacher 的 postPreDestroy 方法执行了
Teacher 的 DisposableBean # destroy 方法执行了

特点非常明显,只有真正的去获取或者销毁 bean 的时候,才会触发。而且销毁 bean 的时候也不会去执行 init-destroy 方法。