Spring Bean生命周期你除了会背八股文面试,真的会用了吗?

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) {

// …

do {

// …

final List currDestroyMethods = new ArrayList<>();

ReflectionUtils.doWithLocalMethods(targetClass, method -> {

// initAnnotationType 即 PostConstruct.class

if (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) {

LifecycleElement element = new LifecycleElement(method);

currInitMethods.add(element);

// …

}

在这个方法里,Spring 将遍历查找被 PostConstruct.class 注解过的方法,返回到上层,并最终调用此方法。

invokeInitMethods 与 InitializingBean 接口

给bean一个机会去响应现在它的所有属性都已设置,并有机会了解它拥有的bean工厂(这个对象)。 这意味着检查 bean 是否实现了 InitializingBean 或自定义了 init 方法。

若是,则调用必要的回调。

invokeInitMethods会判断当前 Bean 是否实现了 InitializingBean 接口,只有实现该接口时,Spring 才会调用该 Bean 的接口实现方法 afterPropertiesSet()。

还有两种方式:

init 方法 && @PostConstruct

实现 InitializingBean 接口,回调afterPropertiesSet()

对于本案例,后两种方案并非最优。

但在一些场景下,这两种方案各有所长。

2 意外触发 shutdown 方法

=================================================================================

类销毁时,也容易写出一堆 bug。

LightService#shutdown,负责关灯:

之前的案例中,若宿管系统重启,灯是不会被关闭的。但随着业务变化,可能会去掉 @Service ,而使用另外一种产生 Bean 的方式:创建一个配置类 BeanConfiguration(标记 @Configuration)来创建一堆 Bean,其中就包含了创建 LightService 类型的 Bean,并将其注册到 Spring 容器:

让 Spring 启动完成后立马关闭当前 Spring 上下文,这就能模拟模拟宿管系统的启停:

以上代码没有其他任何方法的调用,仅是将所有符合约定的类初始化并加载到 Spring 容器,完成后再关闭当前 Spring 容器。

预期:运行后不会有任何log,只改变 Bean 的产生方式。

运行后,控制台打印:

显然 shutdown 方法未按照预期,被执行了,这就导致一个有意思的 bug:

  • 在使用新的 Bean 生成方式之前,每一次宿舍管理服务被重启时,宿舍里所有的灯都不会被关闭

  • 但修改后,只要服务重启,灯都被意外关闭

你能理解这个bug吗?

源码解析


发现:

  • 只有通过使用 Bean 注解注册到 Spring 容器的对象,才会在 Spring 容器被关闭时自动调用 shutdown

  • 使用 @Component将当前类自动注入到 Spring 容器时,shutdown 方法则不会被自动执行

可尝试到 Bean 注解类的代码中去寻找一些线索,可看到属性 destroyMethod。

使用 Bean 注解的方法所注册的 Bean 对象,如果用户不设置 destroyMethod 属性,则其属性值为 AbstractBeanDefinition.INFER_METHOD。

此时 Spring 会检查当前 Bean 对象的原始类中是否有名为 shutdown 或 close 的方法:

  • 有,此方法会被 Spring 记录下来,并在容器被销毁时自动执行

  • 没有,安然无事

查找 INFER_METHOD 枚举值的引用,很容易就找到了使用该枚举值的方法

DisposableBeanAdapter#inferDestroyMethodIfNecessary

private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {

String destroyMethodName = beanDefinition.getDestroyMethodName();

if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||(destroyMethodName == null && bean instanceof AutoCloseable)) {

if (!(bean instanceof DisposableBean)) {

try {

// 尝试查找 close 方法

return bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();

}

catch (NoSuchMethodException ex) {

try {

// 尝试查找 shutdown 方法

return bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName();

}

catch (NoSuchMethodException ex2) {

// no candidate destroy method found

}

}

}

return null;

}

return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null);

}

代码逻辑和

Bean 注解类中对于 destroyMethod 属性的注释:

完全一致。

  • destroyMethodName==INFER_METHOD&&当前类没有实现DisposableBean接口

则先查找类的 close 方法:

  • 找不到

就在抛出异常后继续查找 shutdown 方法

  • 找到

则返回其方法名(close 或者 shutdown)

接着,继续逐级查找引用,最终得到的调用链从上到下为:

  • doCreateBean

  • registerDisposableBeanIfNecessary

  • registerDisposableBean(new DisposableBeanAdapter)

  • inferDestroyMethodIfNecessary

然后,我们追溯到了顶层的 doCreateBean:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)

throws BeanCreationException {

// 实例化 bean

if (instanceWrapper == null) {

instanceWrapper = createBeanInstance(beanName, mbd, args);

}

// …

// 初始化 bean 实例.

Object exposedObject = bean;

try {

populateBean(beanName, mbd, instanceWrapper);

exposedObject = initializeBean(beanName, exposedObject, mbd);

}

// …

// Register bean as disposable.

try {

registerDisposableBeanIfNecessary(beanName, bean, mbd);

}

catch (BeanDefinitionValidationException ex) {

throw new BeanCreationException(

mbd.getResourceDescription(), beanName, “Invalid destruction signature”, ex);

}

return exposedObject;

}

doCreateBean 管理了Bean的整个生命周期中几乎所有的关键节点,直接负责了 Bean 对象的生老病死,其主要功能包括:

  • Bean 实例的创建

  • Bean 对象依赖的注入

  • 定制类初始化方法的回调

  • Disposable 方法的注册

接着,继续查看 registerDisposableBean:

public void registerDisposableBean(String beanName, DisposableBean bean) {

synchronized (this.disposableBeans) {

this.disposableBeans.put(beanName, bean);

}

}

DisposableBeanAdapter 类(其属性destroyMethodName 记录了使用哪种 destory 方法)被实例化

并添加到 DefaultSingletonBeanRegistry#disposableBeans 属性内,disposableBeans 将暂存这些 DisposableBeanAdapter 实例,直到 AnnotationConfigApplicationContext#close被调用。

而当 AnnotationConfigApplicationContext#close被调用时,即当 Spring 容器被销毁时,最终会调用到 DefaultSingletonBeanRegistry#destroySingleton:

  • 遍历 disposableBeans 属性

  • 逐一获取 DisposableBean

  • 依次调用其 close 或 shutdown

public void destroySingleton(String beanName) {

// Remove a registered singleton of the given name, if any.

removeSingleton(beanName);

// Destroy the corresponding DisposableBean instance.

DisposableBean disposableBean;

synchronized (this.disposableBeans) {

disposableBean = (DisposableBean) this.disposableBeans.remove(beanName);

}

destroyBean(beanName, disposableBean);

}

案例调用了 LightService#shutdown 方法,将所有的灯关闭了。

修正


避免在Java类中定义一些带有特殊意义动词的方法来解决。

如果一定要定义名为 close 或者 shutdown 方法,可以将 Bean 注解内 destroyMethod 属性设置为空。如下:

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

@Configuration

public class BeanConfiguration {

@Bean(destroyMethod=“”)

public LightService getTransmission() {

return new LightService();

}

}

为什么 @Service 注入的 LightService,其 shutdown 方法不能被执行?想要执行,则必须要添加 DisposableBeanAdapter,而它的添加是有条件的:

protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) {

AccessControlContext acc = (System.getSecurityManager() != null ? getAccessControlContext() : null);

if (!mbd.isPrototype() && requiresDestruction(bean, mbd)) {

if (mbd.isSingleton()) {

// Register a DisposableBean implementation that performs all destruction

// work for the given bean: DestructionAwareBeanPostProcessors,

// DisposableBean interface, custom destroy method.

registerDisposableBean(beanName,

new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc));

}

else {

//省略非关键代码

}

}

}

关键的语句在于:

!mbd.isPrototype() && requiresDestruction(bean, mbd)

案例代码修改前后,我们都是单例,所以区别仅在于是否满足requiresDestruction 条件。

DisposableBeanAdapter#hasDestroyMethod:

public static boolean hasDestroyMethod(Object bean, RootBeanDefinition beanDefinition) {

if (bean instanceof DisposableBean || bean instanceof AutoCloseable) {

面试结束复盘查漏补缺

每次面试都是检验自己知识与技术实力的一次机会,面试结束后建议大家及时总结复盘,查漏补缺,然后有针对性地进行学习,既能提高下一场面试的成功概率,还能增加自己的技术知识栈储备,可谓是一举两得。

以下最新总结的阿里P6资深Java必考题范围和答案,包含最全MySQL、Redis、Java并发编程等等面试题和答案,用于参考~

重要的事说三遍,关注+关注+关注!

历经30天,说说我的支付宝4面+美团4面+拼多多四面,侥幸全获Offer

image.png

更多笔记分享

历经30天,说说我的支付宝4面+美团4面+拼多多四面,侥幸全获Offer

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
,然后有针对性地进行学习,既能提高下一场面试的成功概率,还能增加自己的技术知识栈储备,可谓是一举两得。

以下最新总结的阿里P6资深Java必考题范围和答案,包含最全MySQL、Redis、Java并发编程等等面试题和答案,用于参考~

重要的事说三遍,关注+关注+关注!

[外链图片转存中…(img-4tPLHbso-1713571131087)]

[外链图片转存中…(img-sui7Zsfh-1713571131087)]

更多笔记分享

[外链图片转存中…(img-WmiW5PTd-1713571131087)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-WPo6U0qi-1713571131088)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值