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

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

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

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

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

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

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

正文

当使用上述代码,构造器参数 LightService 会被自动注入LightService 的 Bean,从而在构造器执行时,避免NPE。

Spring 在类属性完成注入之后,会回调我们定义的初始化方法。即在 populateBean 方法之后,会调用

AbstractAutowireCapableBeanFactory#initializeBean

  • applyBeanPostProcessorsBeforeInitialization处理 @PostConstruct

  • invokeInitMethods处理InitializingBean 接口

两种不同的初始化方案的逻辑

applyBeanPostProcessorsBeforeInitialization与 @PostConstruct

applyBeanPostProcessorsBeforeInitialization 方法最终执行到

InitDestroyAnnotationBeanPostProcessor#buildLifecycleMetadata:

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 {

最后

即使是面试跳槽,那也是一个学习的过程。只有全面的复习,才能让我们更好的充实自己,武装自己,为自己的面试之路不再坎坷!今天就给大家分享一个Github上全面的Java面试题大全,就是这份面试大全助我拿下大厂Offer,月薪提至30K!

我也是第一时间分享出来给大家,希望可以帮助大家都能去往自己心仪的大厂!为金三银四做准备!
一共有20个知识点专题,分别是:

Dubbo面试专题

JVM面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

Java并发面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

Kafka面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

MongDB面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

MyBatis面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

MySQL面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

Netty面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

RabbitMQ面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

Redis面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

Spring Cloud面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

SpringBoot面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

zookeeper面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

常见面试算法题汇总专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

计算机网络基础专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

设计模式专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

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

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

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

SpringBoot面试专题

[外链图片转存中…(img-x5rMU7Mq-1713268519299)]

zookeeper面试专题

[外链图片转存中…(img-MM7SkxEQ-1713268519300)]

常见面试算法题汇总专题

[外链图片转存中…(img-hKJmIRAS-1713268519300)]

计算机网络基础专题

[外链图片转存中…(img-Wsa8xgZ8-1713268519300)]

设计模式专题

[外链图片转存中…(img-n22IbAWN-1713268519301)]

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

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

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

  • 22
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值