【Spring系列】使用InitializingBean和DisposableBean来管理bean生命周期【奔跑吧!JAVA】

举报
陈皮的JavaLib 发表于 2021/05/30 13:00:45 2021/05/30
【摘要】 一个 bean (对象)的生命周期,指的是 bean 从创建,初始化,一系列使用,销毁的过程。只不过。在 Spring 中,bean 的生命周期是由Spring容器管理的。Spring 这种优秀强大的框架,其中一个核心功能就是提供了许多扩展支持。Spring 开放了扩展接口,允许我们自定义 bean 的初始化和销毁方法。

我是陈皮,一个在互联网 Coding 的 ITer,微信搜索「陈皮的JavaLib」第一时间阅读最新文章,回复【资料】,即可获得我精心整理的技术资料,电子书籍,一线大厂面试资料和优秀简历模板。


一、Bean 的生命周期

我们知道,一个 bean (对象)的生命周期,指的是 bean 从创建,初始化,一系列使用,销毁的过程。只不过。在 Spring 中,bean 的生命周期是由Spring容器管理的。Spring 这种优秀强大的框架,其中一个核心功能就是提供了许多扩展支持。既然 Srping 容器管理了所有的 Spring bean,那肯定也会开放一些扩展点让我们进行自定义扩展。今天来讲讲 bean 的初始化和销毁的方法。Spring 开放了扩展接口,允许我们自定义 bean 的初始化和销毁方法。即当 Spring 容器在 bean 进行到相应的生命周期阶段时,会自动调用我们自定义的初始化和销毁方法。这两个扩展接口即是 InitializingBeanDisposableBean

二、InitializingBean 接口说明

InitializingBean 接口为 bean 提供了 bean 属性初始化后的处理方法,它只有 afterPropertiesSet 一个方法,凡是实现此接口的类,在 bean 的属性初始化后都会执行该方法。

package org.springframework.beans.factory;

public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

例如 UserServiceImpl 这个类实现 InitializingBean 接口,并重写 afterPropertiesSet 方法。

public class UserServiceImpl implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("--- afterPropertiesSet 方法");
    }
}

三、DisposableBean 接口说明

DisposableBean 接口为单例 bean 提供了在容器销毁 bean 时的处理方法,它只有 destroy 一个方法,凡是实现此接口的类,在 bean 被销毁时都会执行该方法。

package org.springframework.beans.factory;

public interface DisposableBean {
    void destroy() throws Exception;
}

例如 UserServiceImpl 这个类实现 DisposableBean 接口,并重写 destroy 方法。

public class UserServiceImpl implements DisposableBean {
    @Override
    public void destroy() throws Exception {
        System.out.println("--- destroy 方法");
    }
}

四、Bean 初始化

使用实现 InitializingBean 接口,重写 afterPropertiesSet 方法的方式,会让代码和 Spring 紧耦合,如果你不想代码和 Spring 耦合,那不推荐使用此种方式。

另外一种方式是,配置 bean 的时候通过配置 initMethod 指定 bean 的初始化方法,它也是在 bean 属性初始化之后需要执行的初始化方法。

第一种方式是将 bean 强制转换成 InitializingBean 接口类型,然后直接调用 afterPropertiesSet 方法,速度更快,第二种方式是通过反射来执行 initMethod 方法,效率相对较低。

afterPropertiesSet 和 initMethod 可以同时存在,但是 afterPropertiesSet 方法是在 initMethod 方法之前执行的。

所以一个 bean 从创建到初始化的过程可以总结为:

  1. 通过构造器创建 bean
  2. 属性注入
  3. 执行 afterPropertiesSet 方法
  4. 执行 initMethod 方法

定义一个 UserService 接口,规范问题,面向接口编程。

public interface UserService {
    void test();
}

定义一个 bean 类,实现 InitializingBean 接口。

public class UserServiceImpl implements UserService, InitializingBean {

    private UserDao userDao;

    public UserServiceImpl() {
        System.out.println("--- UserServiceImpl 构造方法");
    }

    @Autowired
    public void setUserDao(UserDao userDao) {
        System.out.println("--- setUserDao 属性注入方法");
        this.userDao = userDao;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("--- afterPropertiesSet 方法");
    }

    public void initMethod() {
        System.out.println("--- initMethod 方法");
    }
    
    @Override
    public void test() {
    }
}

配置 bean,生成 UserServiceImpl 类的 bean,交由 Spring 容器管理。

@Configuration
public class UserServiceImplConfig {
	// 通过 initMethod 属性指定初始化方法
    @Bean(initMethod = "initMethod")
    public UserServiceImpl userServiceImpl() {
        return new UserServiceImpl();
    }
}

启动服务,会在启动日志看到如下日志,表示在 bean 按指定顺序创建和初始化了。

--- UserServiceImpl 构造方法
--- setUserDao 属性注入方法
--- afterPropertiesSet 方法
--- initMethod 方法

五、Bean 销毁

使用实现 DisposableBean 接口,重写 destroy 方法的方式,同样也会让代码和 Spring 紧耦合,如果你不想代码和 Spring 耦合,那不推荐使用此种方式。

另外一种方式是,配置 bean 的时候通过配置 destroyMethod 指定 bean 的销毁方法,它也是在 bean 被销毁时需要执行的方法。

第一种方式是将 bean 强制转换成 DisposableBean 接口类型,然后直接调用 destroy 方法,速度更快,第二种方式是通过反射来执行 destroyMethod 方法,效率相对较低。

destroy 和 destroyMethod 可以同时存在,但是 destroy 方法是在 destroyMethod 方法之前执行的。

我们将上面 UserServiceImpl 类修改如下,使之实现 接口,重写 destroy 方法。

public class UserServiceImpl implements UserService, InitializingBean, DisposableBean {

    private UserDao userDao;

    public UserServiceImpl() {
        System.out.println("--- UserServiceImpl 构造方法");
    }

    @Autowired
    public void setUserDao(UserDao userDao) {
        System.out.println("--- setUserDao 属性注入方法");
        this.userDao = userDao;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("--- afterPropertiesSet 方法");
    }

    public void initMethod() {
        System.out.println("--- initMethod 方法");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("--- destroy 方法");
    }

    public void destroyMethod() {
        System.out.println("--- destroyMethod 方法");
    }

    @Override
    public void test() {
    }
}

配置 bean,同时指定 destroyMethod 方法,生成 UserServiceImpl 类的 bean,交由 Spring 容器管理。

@Configuration
public class UserServiceImplConfig {
	// 通过 initMethod 属性指定初始化方法,通过 destroyMethod 属性指定销毁时执行的方法
    @Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")
    public UserServiceImpl userServiceImpl() {
        return new UserServiceImpl();
    }
}

启动服务成功之后,停掉服务,会在后台日志看到如下日志,表示在 bean 按指定顺序创建,初始化和销毁了。

--- UserServiceImpl 构造方法
--- setUserDao 属性注入方法
--- afterPropertiesSet 方法
--- initMethod 方法
2021-02-19 22:42:28.926  INFO 5844 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-02-19 22:42:29.748  INFO 5844 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-02-19 22:42:29.782  INFO 5844 --- [           main] com.nobody.Application                   : Started Application in 20.12 seconds (JVM running for 25.131)
2021-02-19 22:42:43.932  INFO 5844 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'
--- destroy 方法
--- destroyMethod 方法

六、源码分析

我们已经知道实现了 InitializingBean 和 DisposableBean 接口的 bean,会在 bean 初始化和销毁的时候分别执行这两个方法,那么具体是如何执行的呢?那我们从 Spring 源码分析一探究竟。

看过 Spring 源码的同学肯定知道,InitializingBean 的 afterPropertiesSet 调用,其实奥妙就在 Spring 加载 bean 时的 AbstractAutowireCapableBeanFactory 类,其中有个方法 invokeInitMethods 如下:

protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd) throws Throwable {
    // 判断该 bean 是否实现了 InitializingBean 接口,如果是,则会调用 bean 的 afterPropertiesSet 方法
    boolean isInitializingBean = bean instanceof InitializingBean;
    if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
        }

        if (System.getSecurityManager() != null) {
            try {
                AccessController.doPrivileged(() -> {
                    // 调用 afterPropertiesSet 方法
                    ((InitializingBean)bean).afterPropertiesSet();
                    return null;
                }, this.getAccessControlContext());
            } catch (PrivilegedActionException var6) {
                throw var6.getException();
            }
        } else {
            // 调用afterPropertiesSet 方法
            ((InitializingBean)bean).afterPropertiesSet();
        }
    }

    if (mbd != null && bean.getClass() != NullBean.class) {
        // 获取 initMethod 方法名
        String initMethodName = mbd.getInitMethodName();
        // 如果指定了 initMethod 方法,并且不是 afterPropertiesSet 方法,则执行 initMethod
        if (StringUtils.hasLength(initMethodName) && (!isInitializingBean || !"afterPropertiesSet".equals(initMethodName)) 
                && !mbd.isExternallyManagedInitMethod(initMethodName)) {
            // 通过反射方式,调用 initMethod 指定的方法
            this.invokeCustomInitMethod(beanName, bean, mbd);
        }
    }
}

那 bean 被销毁时,执行的 destroy 方法又是在哪里调用的呢? 其实当 Spring 容器销毁时,会将容器中的所有单例 bean 先全部销毁,在 AbstractApplicationContext 中的 destroyBeans() 方法就是用来处理销毁bean的。源码如下:

protected void destroyBeans() {
    this.getBeanFactory().destroySingletons();
}
public void destroySingleton(String beanName) {
   // 从三级缓存中将 bean 删除
    this.removeSingleton(beanName);
    DisposableBean disposableBean;
    synchronized(this.disposableBeans) {
        // 如果实现了 DisposableBean 接口,强制转为 DisposableBean 对象
        disposableBean = (DisposableBean)this.disposableBeans.remove(beanName);
    }
	// 执行 destroy 方法
    this.destroyBean(beanName, disposableBean);
}
protected void destroyBean(String beanName, @Nullable DisposableBean bean) {
        // 省略代码

		// 如果 DisposableBean bean对象不为空,则执行 destroy 方法。
        if (bean != null) {
            try {
                bean.destroy();
            } catch (Throwable var13) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Destruction of bean with name '" + beanName + "' threw an exception", var13);
                }
            }
        }

        // 省略代码
    }

七、使用误区

如果 bean 不是交由 Spring 管理的,是我们自己手动创建(new)出来的,是不会自动调用我们定义的初始化和销毁方法的。例如如下:

@GetMapping("find")
public String find() {
    UserService userService1 = new UserServiceImpl();
    return "ok";
}

调用此接口,只会调用它的构造器方法。

--- UserServiceImpl 构造方法

其二,如果 bean 不是单例的,而是 prototype 的,那每次从 Spring 容器获取创建 bean 时,会调用初始化方法,在销毁 bean 对象时,不会执行销毁方法。因为 prototype 类型的 bean,创建是 Spring 创建的,但是创建之后它就不进行维护了。

此演示项目已上传到Github,如有需要可自行下载,欢迎 Star 。

https://github.com/LucioChn/spring-annotation


【奔跑吧!JAVA】有奖征文火热进行中:https://bbs.huaweicloud.com/blogs/265241

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。