通常,bean的初始化和销毁方法我们有三个地方可以入手,分别是:
bean的生命周期:bean创建---初始化----销毁的过程
下面分别介绍上面提到的三种和初始化,销毁相关的接口,方法
容器管理bean的生命周期:我们可以自定义初始化和销毁方法;容器在bean进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法
构造(对象创建):
销毁:
在xml配置中,可以通过init-method和destory-method指定初始化方法和销毁方法。该方法必须没有参数,但是可以抛出异常。
在现在流行的注解配置方式中,可以通过@Bean(initMethod="init",destroyMethod="destory")的方式指定bean的初始化方法和销毁方法。
定义Toyota类,然后定义两个方法,一个是我们想作为初始化方法的init方法,一个是我们想作为销毁方法的destory方法。
public class Toyota {
public Toyota() {
System.out.println("Toyota constructor...");
}
public void init() {
System.out.println("Toyota ... init...");
}
public void detory() {
System.out.println("Toyota ... destory...");
}
}
将Toyota添加到配置类中
@Configuration
public class LifeCycleConfig {
@Bean(initMethod="init",destroyMethod="detory")
public Toyota toyota(){
return new Toyota();
}
}
测试并打印容器中的bean信息:
@Test
public void testCar() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
printBeans(context);
context.close();
}
private void printBeans(ApplicationContext context) {
String[] defBeans = context.getBeanDefinitionNames();
for (String name : defBeans) {
System.out.println(name);
}
}
执行查看结果:
可以看到,在容器启动时,执行了Toyota的无参构造方法,然后紧接着执行了自定义bean的初始化方法init方法。
在容器关闭时,执行了自定义bean的销毁方法destory方法。
这里需要注意的是:@Bean默认是单例的,如果将scope改为多实例的,那执行结果就不是这样了,请看下面的代码:
@Configuration
public class LifeCycleConfig {
@Bean(initMethod = "init", destroyMethod = "detory")
@Scope("prototype")
public Toyota toyota() {
return new Toyota();
}
}
执行的结果如下:
可以看到,容器启动时,并没有实例化和初始化Toyota对象,在容器关闭时,也没有调用destory方法。
此时,我们可以大胆猜测(肯定),只有单例的bean,在容器创建时才会实例化,并执行初始化方法,在容器关闭时执行销毁方法。对于多实例bean,只有在创建bean的时候才会实例化并初始化方法,如果要执行多实例bean的销毁方法,需要我们自己手动去调用。
@Test
public void testCar() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
printBeans(context);
context.getBean("toyota");
context.close();
}
private void printBeans(ApplicationContext context) {
String[] defBeans = context.getBeanDefinitionNames();
for (String name : defBeans) {
System.out.println(name);
}
}
}
通过让Bean实现InitializingBean(定义初始化逻辑),DisposableBean(定义销毁逻辑);
我们还是在之前的Toyota类代码上继续我们的代码编写
public class Toyota implements InitializingBean, DisposableBean {
public Toyota() {
System.out.println("Toyota constructor...");
}
public void init() {
System.out.println("define....Toyota ... init...");
}
public void detory() {
System.out.println("define....Toyota ... detory...");
}
@Override
public void destroy() throws Exception {
System.out.println("DisposableBean....Toyota ... destroy...");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean....Toyota ... afterPropertiesSet...");
}
}
实现了InitializingBean接口,我们需要重写afterPropertiesSet方法,同样,实现了DisposableBean接口,我们需要重写destroy方法。既然用了这两个接口,那我们就一起看一下这两个接口的源码以及说明吧:
InitializingBean:
看源码的注释,我们知道,afterPropertiesSet的调用时机是在beanFactory(bean工厂)实例化bean后,将所有的属性值设置完毕之后,才会调用该接口方法。通过方法的申明,我们可以发现,该方法是一个无参方法,同时,在调用过程中可以抛出异常。
同样,我们看一下DisposableBean接口的源码:
通过源码,我们可以知道,destory方法的调用时机是在beanFactory关闭且bean为单例的对象时触发。通过注释,也可以发现,该方法可以抛出异常,但是异常并不会重复抛出,这样是为了保证其他的bean也可以释放他们自己的资源。
好了,插曲先到这,我们继续看我们的Toyota类,看看调用时会发生什么情况。
首先看当Toyota为单例时的执行结果:
可以看到,
再看一下Toyota为多实例的执行结果:
可以看到,多实例初始化方法的执行顺序和单例是一样的,但是并没有执行销毁方法,不管是自定义的还是DisposableBean的销毁方法。
可以使用JSR250:
还是通过我们的代码来看问题,继续在上面的Toyota代码上修改:
public class Toyota implements InitializingBean, DisposableBean {
public Toyota() {
System.out.println("Toyota constructor...");
}
public void init() {
System.out.println("define....Toyota ... init...");
}
public void detory() {
System.out.println("define....Toyota ... detory...");
}
@Override
public void destroy() throws Exception {
System.out.println("DisposableBean....Toyota ... destroy...");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean....Toyota ... afterPropertiesSet...");
}
@PostConstruct
public void PostConstruct() {
System.out.println("JSR250....PostConstruct....Toyota ... PostConstruct...");
}
@PreDestroy
public void PreDestroy() {
System.out.println("JSR250....PreDestroy....Toyota ... PreDestroy...");
}
}
先查看Toyota为单例时的执行结果:
可以看到:
再查看Toyota为多实例时的执行结果:
可以看到,初始化顺序和单例时是一样的顺序,但是并没有执行销毁方法,如果需要执行销毁方法,需要我们手动调用执行。
BeanPostProcessor是bean的后置处理器,在bean初始化前后进行一些处理工作:
直接观察代码,会发现BeanPostProcessor的方法postProcessBeforeInitialization有返回类型Object,
postProcessAfterInitialization也有返回类型Object,同时两个方法都可以抛出BeansException异常。
Spring底层对 BeanPostProcessor 的使用,比如bean赋值,注入其他组件,@Autowired,生命周期注解功能,@Async,xxx BeanPostProcessor;
这么重要的东西,我们还是通过demo来研究。
我们继续在之前的代码上修改:
首先定义一个我们自己的BeanPostProcessor
@Component
public class LifeCycleBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Toyota) {
System.err.println("postProcessBeforeInitialization....拦截指定bean");
}
System.out.println("这句话证明所有容器中的bean都会被postProcessBeforeInitialization拦截.. beanName=" + beanName + "==>" + bean);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Toyota) {
System.err.println("postProcessAfterInitialization.....拦截指定bean");
}
System.out.println("这句话证明所有容器中的bean都会被postProcessAfterInitialization拦截.. beanName=" + beanName + "==>" + bean);
return bean;
}
}
接着,将我们自定义的BeanPostProcessor添加配置到容器中
@Configuration
@ComponentScan("com.ddc.fw.spring.annotation.demo.BeanPostProcessor") // 扫描自定义BeanPostProcessor
public class LifeCycleConfig {
@Bean(initMethod = "init", destroyMethod = "detory")
// @Scope("prototype")
public Toyota toyota() {
return new Toyota();
}
}
最后,来让我们看看执行的时机吧:
第一个标示点:说明所有容器加载的bean在实例化之后,初始化之前都会执行postProcessBeforeInitialization方法,在初始化完成后执行postProcessAfterInitialization方法。
第二个标示点:说明我们可以只拦截我们指定的bean,并且执行的顺序是:
以上九点,按顺序执行,完成bean的初始化和销毁工作。
需要注意的是,BeanPostProcessor提供的两个方法,是针对初始化前后的拦截操作,和容器的关闭,bean的销毁无关。