你好,这里是codetrend专栏“Spring6全攻略”。
Spring框架设计生命周期回调函数的主要目的是为了提供一种机制,使开发人员能够在对象创建、初始化和销毁等生命周期阶段执行特定的操作。这种机制可以帮助开发人员编写更加灵活和可维护的代码。
举个例子。
缓存预热是一种在程序启动或缓存失效之后,主动将热点数据加载到缓存中的策略。
通过缓存预热能避免第一次查询数据慢的问题。
那如何在应用启动的时候把数据全量写入缓存这呢?
这个时候就可以用到Spring的生命周期函数。
在服务创建的时候写一个init函数,加上注解@PostConstruct
之后,就会在应用启动的时候调用。
这样一旦数据没有在缓存,就会二次写入。
整个过程用mermaid表示如下:
生命周期函数有哪些使用场景
Spring框架的生命周期回调函数有多种使用场景,以下是一些常见的情况:
- 初始化资源:在Bean初始化之后,可能需要进行一些资源的初始化操作,比如建立数据库连接、加载配置信息等。通过初始化回调函数,可以在Bean准备就绪后执行这些操作。
- 释放资源:在Bean销毁之前,可能需要进行一些资源的释放操作,比如关闭数据库连接、释放文件句柄等。通过销毁回调函数,可以在Bean即将被销毁时执行这些清理操作。
- 依赖注入后的处理:有时候在依赖注入完成后需要执行特定的逻辑,例如根据依赖的情况进行一些动态调整或者校验。
- 与外部系统集成:在与外部系统集成时,可能需要在Bean创建后或销毁前执行一些初始化或清理工作,例如注册到消息队列、向外部服务发送初始化请求等。
- 日志记录:使用生命周期回调函数可以方便地记录Bean的创建、初始化和销毁等生命周期事件,以便进行调试和排查问题。
- 定时任务:通过生命周期回调函数可以实现定时任务的启动和关闭,例如在应用启动时启动定时任务,在应用关闭时停止定时任务。
有哪些生命周期回调
默认的回调函数有如下几种:
- 初始化回调:在Bean对象实例化后、属性注入完成之后,执行特定的初始化操作的过程。
- 销毁回调:在Bean对象即将被销毁前执行特定的清理操作的过程。
- 启动和停止回调:在整个Spring应用程序上下文启动和停止时执行的回调方法。
除此之外还可以通过实现接口BeanPostProcessor
来完成任意的回调函数。
初始化回调
在Spring中,Bean的初始化回调可以通过实现InitializingBean接口、@PostConstruct注解或在XML配置中使用init-method来实现。下面将详细说明各种方式的用法,并举例说明。
实现InitializingBean接口:
- 实现InitializingBean接口的类需要实现afterPropertiesSet()方法,在该方法中编写初始化逻辑。
- 示例代码如下:
@Slf4j
class MovieFinder implements InitializingBean {
public List<String> findMovies() {
return Arrays.asList("电影1", "电影2", "电影3");
}
@Override
public void afterPropertiesSet() throws Exception {
log.info("电影数据初始化中...");
}
}
使用@PostConstruct注解:
- 使用javax.annotation.PostConstruct注解标记一个方法作为初始化方法,在依赖注入完成后会自动调用该方法。
- 把上面的代码稍微改造下,示例代码如下:
@Slf4j
class MovieFinder implements InitializingBean {
public List<String> findMovies() {
return Arrays.asList("电影1", "电影2", "电影3");
}
@Override
public void afterPropertiesSet() throws Exception {
log.info("电影数据初始化中...");
}
@PostConstruct
public void init() {
// 初始化逻辑
log.info("电影数据初始化中...通过PostConstruct");
}
}
XML配置init-method:
- 在XML配置中,可以通过init-method属性指定Bean的初始化方法,在Bean实例化后会调用该方法。
- XML配置示例:
<bean id="myBean" class="com.example.MyBean" init-method="init">
</bean>
public class MyBean {
public void init() {
// 初始化逻辑
System.out.println("MyBean is being initialized.");
}
}
源码分析:
Spring的调用链路很长,按顺序执行的方法如下:
AbstractAutowireCapableBeanFactory#createBean
AbstractAutowireCapableBeanFactory#doCreateBeanAbstractAutowireCapableBeanFactory#doCreateBean
AbstractAutowireCapableBeanFactory#initializeBean
AbstractAutowireCapableBeanFactory#invokeInitMethods
doCreateBean
调用了两个核心函数,其中第二个就是初始化函数。
// 给bean的属性设置一些逻辑
populateBean(beanName, mbd, instanceWrapper);
// 初始化逻辑,这块就是执行初始化回调的地方
exposedObject = initializeBean(beanName, exposedObject, mbd);
其中初始化的核心代码就是这段。
protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd)
throws Throwable {
// 解析实现了InitializingBean,也就是调用afterPropertiesSet
boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.hasAnyExternallyManagedInitMethod("afterPropertiesSet"))) {
if (logger.isTraceEnabled()) {
logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
}
((InitializingBean) bean).afterPropertiesSet();
}
// 解析各种初始化方法,自定义的、注解注入的
if (mbd != null && bean.getClass() != NullBean.class) {
String[] initMethodNames = mbd.getInitMethodNames();
if (initMethodNames != null) {
for (String initMethodName : initMethodNames) {
if (StringUtils.hasLength(initMethodName) &&
!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.hasAnyExternallyManagedInitMethod(initMethodName)) {
invokeCustomInitMethod(beanName, bean, mbd, initMethodName);
}
}
}
}
}
销毁回调
@PreDestroy
注解
- 功能:允许开发者通过注解标记 Bean 销毁时应执行的方法。
- 优点:简单直观,符合 Java 标准,易于使用。
- 使用场景:适用于需要在 Bean 销毁前执行一些清理操作,如关闭资源等。
实现 DisposableBean
接口
- 功能:提供了一个回调接口,要求实现
destroy
方法来处理 Bean 销毁时的逻辑。 - 优点:接口方式,强制性较强,适合需要明确销毁逻辑的场景。
- 使用场景:适用于需要在 Bean 销毁前执行复杂操作或依赖其他 Spring Bean 的情况。
自定义销毁方法:
- 功能:允许在配置类中指定 Bean 的销毁方法。
- 优点:灵活性高,方法名可以自由定义。
- 使用场景:适用于需要灵活配置的 Bean 销毁逻辑,尤其是通过 Java 配置类定义 Bean 的情况。
Bean代码如下:
/**
* 服务代码
*/
@Slf4j
class SimpleMovieLister implements DisposableBean {
private final MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
public void listMovies() {
log.info("电影列表打印中");
movieFinder.findMovies().forEach(log::info);
}
@PreDestroy
public void onDestroy() {
log.info("Bean is being destroyed");
}
@Override
public void destroy() throws Exception {
log.info("DisposableBean is being destroyed");
}
public void customDestroy() {
log.info("Custom destroy method is being called");
}
}
APP配置如下:
/**
* App配置
*/
@Configuration
class ConstructorAppConfig{
@Bean
public MovieFinder movieFinder() {
return new MovieFinder();
}
// destroyMethod属性能指定自定义属性
@Bean(destroyMethod = "customDestroy")
public SimpleMovieLister simpleMovieLister(MovieFinder movieFinder) {
return new SimpleMovieLister(movieFinder);
}
}
解析销毁方法需要 CommonAnnotationBeanPostProcessor
,这里就在启动类手动注入了对应的处理器。想要触发还需要手动close对应的bean工厂。
/**
* bean生命周期自定义
* @author nine
* @since 1.0
*/
@Slf4j
public class BeanLifeCycleDemo {
public static void main(String[] args) {
// 创建一个基于 Java Config 的应用上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConstructorAppConfig.class);
// @Resource @PostConstruct @PreDestroy
context.registerBean(CommonAnnotationBeanPostProcessor.class);
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
log.info("beanDefinitionName: {}", beanDefinitionName);
}
log.info("bean初始化完成");
// 从上下文中获取名bean,其类型为PetStoreService
SimpleMovieLister bean = context.getBean(SimpleMovieLister.class);
// 调用获取的bean的方法
bean.listMovies();
// 销毁容器
context.close();
}
}
相关源码DefaultSingletonBeanRegistry#destroyBean
片段如下:
/**
* 销毁指定名称的 bean 及其相关处理
* @param beanName 要销毁的 bean 的名称
* @param bean 可销毁的 bean 对象(可能为 null)
*/
protected void destroyBean(String beanName, @Nullable DisposableBean bean) {
// 首先触发依赖该 bean 的其他 bean 的销毁...
Set<String> dependencies;
synchronized (this.dependentBeanMap) { // 在完全同步块内以确保获取到独立的集合
dependencies = this.dependentBeanMap.remove(beanName);
}
if (dependencies!= null) {
if (logger.isTraceEnabled()) {
logger.trace("Retrieved dependent beans for bean '" + beanName + "': " + dependencies);
}
for (String dependentBeanName : dependencies) {
destroySingleton(dependentBeanName); // 销毁依赖的单例
}
}
// 现在实际销毁该 bean...
if (bean!= null) {
try {
bean.destroy(); // 调用可销毁 bean 的销毁方法
}
catch (Throwable ex) {
if (logger.isWarnEnabled()) {
logger.warn("Destruction of bean with name '" + beanName + "' threw an exception", ex);
}
}
}
// 触发包含在该 bean 中的 bean 的销毁...
Set<String> containedBeans;
synchronized (this.containedBeanMap) { // 在完全同步块内以确保获取到独立的集合
containedBeans = this.containedBeanMap.remove(beanName);
}
if (containedBeans!= null) {
for (String containedBeanName : containedBeans) {
destroySingleton(containedBeanName); // 销毁包含的单例
}
}
// 从其他 bean 的依赖中移除已销毁的 bean。
synchronized (this.dependentBeanMap) {
for (Iterator<Map.Entry<String, Set<String>>> it = this.dependentBeanMap.entrySet().iterator(); it.hasNext();) {
Map.Entry<String, Set<String>> entry = it.next();
Set<String> dependenciesToClean = entry.getValue();
dependenciesToClean.remove(beanName);
if (dependenciesToClean.isEmpty()) {
it.remove();
}
}
}
// 移除已销毁 bean 的预准备依赖信息。
this.dependenciesForBeanMap.remove(beanName);
}
可以看到Spring 会在 Bean 销毁时调用 destroy
方法。
启动和关闭回调
在 Spring 框架中,Startup 和 Shutdown Callbacks 提供了在容器启动和关闭时执行特定操作的功能。
Startup Callbacks(启动回调):
- 允许开发者在 Spring 应用程序启动时执行特定的操作,如初始化缓存、启动定时任务等。
- 这些回调方法通常与 Bean 的初始化相关联,在容器启动后执行。
Shutdown Callbacks(关闭回调):
- 允许开发者在 Spring 应用程序关闭时执行特定的操作,如释放资源、关闭连接等。
- 这些回调方法通常与 Bean 的销毁相关联,在容器关闭前执行。
Spring 框架实现了这一功能通过以下几个关键点:
SmartLifecycle
接口
- Spring 提供了
SmartLifecycle
接口,允许 Bean 实现该接口以自定义它们的启动和关闭逻辑。实现了该接口的 Bean 在容器启动和关闭时会被自动调用。
实现 SmartLifecycle
接口:
import org.springframework.context.SmartLifecycle;
import org.springframework.stereotype.Component;
@Component
public class MyLifecycleBean implements SmartLifecycle {
private boolean isRunning = false;
@Override
public void start() {
System.out.println("Bean is starting...");
isRunning = true;
}
@Override
public void stop() {
System.out.println("Bean is stopping...");
isRunning = false;
}
@Override
public boolean isRunning() {
return isRunning;
}
}
在Bean工厂运行的时候就会触发对应的生命周期函数。
关于作者
来自一线全栈程序员nine的探索与实践,持续迭代中。
欢迎关注或者点个收藏~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。