深入解析Spring Bean初始化时和销毁时的一些扩展点

一.前言

今天来分享一下Bean在初始化时和Bean销毁时我们可以做的一些操作,如果只是单纯做CRUD开发,那么这些操作基本上不可能遇到,如果依赖于Spring来做一些框架层面的开发或者中间件开发,那么这些操作是很常用的,在Bean进行初始化或者销毁的时候,如果我们需要做一些操作,比如加载和销毁一些资源或者执行一些方法时,那么就可以使用Spring提供的一些扩展,今天主要分享初始化Bean时的三种方式和销毁Bean时的三种方式。

二.相关扩展点和方法

初始化时和销毁时都有相应的方式供我们选择,下面列出了初始化时和销毁时的各三种方式,然后再进行深度解析。

初始化时

  • @PostConstruct
  • 自定义初始化方法
  • InitializingBean

销毁时

  • @PreDestroy
  • 自定义销毁方法
  • DisposableBean

三.测试

定义Bean

下面我们定义了一个Bean,实现了InitializingBean和DisposableBean接口,分别在方法上使用了@PostConstruct注解和@PreDestroy注解,又自定义了初始化方法initMethod()和销毁方法destoryMethod()。

配置Bean

使用@Configuration注解和@Bean注解来注册Bean,我们在InitDestroyBean上使用了@Bean注解来将其标注为一个Bean,并且加上了初始化方法和销毁方法。

查看结果

从控制台输出我们可以得出这些方式的优先级

  • @PostConstruct > InitializingBean > 自定义初始化方法

  • @PreDestroy > DisposableBean > 自定义销毁方法

四.源码解析

下面进行源码解析,因为Spring的源码还是比较复杂,所以我们只从最关键的地方开始分析,下分析初始化Bean时,再分析销毁Bean时。

初始化Bean

1.解析Bean中@PostConstruct注解和@PreDestory注解

我们直接来到AbstractAutowireCapableBeanFactory类中,@PostConstruct注解和@PreDestory标注的方法会在applyMergedBeanDefinitionPostProcessors中被后置处理器InitDestroyAnnotationBeanPostProcessor解析,原理是通过反射判断Bean中是否有方法上标注了@PostConstruct注解和@PreDestory注解,如果有,则将其加入initMethodsdestroyMethods集合中,然后组装到LifecycleMetadata中,以供后续使用。

2.对Bean进行初始化-调用标注@PostConstruct的方法

下一步进入initializeBean方法中,然后进入applyBeanPostProcessorsBeforeInitialization方法,从名字我们可以看出这是Bean初始化前操作,这里会调用InitDestroyAnnotationBeanPostProcessor后置处理进行处理。

从上面可以看出会通过findLifecycleMetadata去获取元数据,就是上面我们说的LifecycleMetadata,这里会用到,然后调用invokeInitMethods方法,最终会通过反射调用到标注了@PostConstruct注解的方法。

从上面我们可以看出,标注了@PostConstruct注解的方法最先执行。

3.调用自定义初始化方法和实现了InitializingBean接口的方法。

接着调用invokeInitMethods方法,里面会判断Bean是否实现了InitializingBean接口,如果实现,那么就会调用方法afterPropertiesSet(),接着会获取Bean中自定义的初始化方法,然后通过反射调用。

从上面看出,实现了InitializingBean接口中的最先被执行,自定义的Bean初始化方法第二被执行。

4.总结

从上面看出,如果是通过@PostConstruct注解标注的方法,则需要使用后置处理器BeanPostProcessor来进行处理,实现InitializingBean接口和自定义的初始化方法则不需要使用后置处理器处理,@PostConstruct标注的方法的优先级大于实现了InitializingBean接口的方法,实现了InitializingBean接口的方法大于自定义的初始化方法。

销毁Bean

销毁Bean的动作发生在容器关闭的时候,当Spring程序中发生BeansException异常是会触发,还有我们也可以手动关闭容器,关闭容器后,Spring中的所有Bean都会被清理掉,这时候如果再去获取对应的Bean,就会发生异常。

1.手动关闭容器

为了去分析源码,我们这里直接手动去关闭Spring容器,直接调用close()方法关闭容器。

手动调用关闭容器后,会去调用doClose()方法,然后里面有一个destroyBeans()方法,这里方法就是销毁Bean,我们可以看到它有一个备注Destroy all cached singletons in the context's BeanFactory.,意思就是销毁单例Bean,至于为什么是销毁单例Bean,大家可以想一下,哈哈!

2.执行标注了@PreDestroy的方法

顺着源码一直跟进来,我们发现它它也会调用Bean的后置处理器,然后通过反射调用标注了@PreDestroy注解的方法,这里和标注了@PostConstruct的方法的执行是一样的。

3.调用实现DisposableBean接口的方法

接着判断当前的Bean是否实现了DisposableBean接口,如果实现了,则调用destroy()方法,和InitializingBean也是一样的套路。

4.执行自定义的销毁方法

往下执行,就会判断是否定义了自定义的销毁方法,如果定义了,则通过反射进行调用,和初始化方法哪里是一样的套路。

5.总结

从上面可以看出,销毁Bean和初始化Bean时这些扩展点的方式基本上都差不多,在销毁Bean时,会将其中涉及到的装Bean的一些集合都进行清空,然后再把BeanFactory关闭,不过我们这里关注的时销毁时执行的方法,就不去管那些了。

我们得出结论,标注了@PreDestroy注解的方法最先被执行,实现了DisposableBean接口的第二被执行,自定义的销毁方法最后被执行。

五.思考

我们思考一下,为什么Spring对于@PostConstruct注解和@PreDestory注解要使用专门的后置处理器来处理?

其实这也是Spring牛逼的地方,扩展性极强,因为@PostConstruct注解和@PreDestory注解其实不是属于Spring的,而是Java语言层面的注解,如果不通过扩展的方式来实现这两个注解的使用,那么就没有扩展性而言,加入那天再需要加入Java层面的注解到Spring中,那么又需要去代码里面改,显然,这样的设计时不合理的。

像@Resource注解也不是Spring的,也是Java层面的,处理这个注解也时通过后置处理器来进行处理。

所以Spring为什么发展得这么迅猛,Java程序员基本没人不用Spring,Spring的不断发展,使它成为最复杂的框架,但是它的内核设计还是十分优秀的,如果没有优秀的设计,估计代码已经不堪入目了。

6.总结

上面我们对于Spring的Bean初始化时和销毁时的一些操作进行了介绍并进行测试,然后分析了它们的原理,并对Spring的设计进行我个人的理解和评价。

其实对于像Spring这样庞大的框架,学习难度还是比较大的,需要我们一遍有一遍去debug,去分析,去理解,这样才能慢慢对它有一个了解,如果只是为了去应付,去背,那么基本上没用。

今天的分享就到这里,感谢你的观看,我们下期见,如果文中有不合理或者需要补充的地方,希望得到你的指点!

相关推荐
唐叔在学习4 分钟前
就算没有服务器,我照样能够同步数据
后端·python·程序员
用户68545375977691 小时前
同步成本换并行度:多线程、协程、分片、MapReduce 怎么选才不踩坑
后端
javaTodo1 小时前
Claude Code 记忆机制详解:从 CLAUDE.md 到 Auto Memory,六层体系全拆解
后端
LSTM971 小时前
使用 C# 和 Spire.PDF 从 HTML 模板生成 PDF 的实用指南
后端
JaguarJack1 小时前
为什么 PHP 闭包要加 static?
后端·php·服务端
BingoGo2 小时前
为什么 PHP 闭包要加 static?
后端
是糖糖啊2 小时前
OpenClaw 从零到一实战指南(飞书接入)
前端·人工智能·后端
百度Geek说2 小时前
基于Spark的配置化离线反作弊系统
后端
Java编程爱好者3 小时前
虚拟线程深度解析:轻量并发编程的未来趋势
后端