浅浅了解下Spring中生命周期函数(Spring6全攻略)

你好,这里是codetrend专栏"Spring6全攻略"。

Spring框架设计生命周期回调函数的主要目的是为了提供一种机制,使开发人员能够在对象创建、初始化和销毁等生命周期阶段执行特定的操作。这种机制可以帮助开发人员编写更加灵活和可维护的代码。

举个例子。

缓存预热是一种在程序启动或缓存失效之后,主动将热点数据加载到缓存中的策略。

通过缓存预热能避免第一次查询数据慢的问题。

那如何在应用启动的时候把数据全量写入缓存这呢?

这个时候就可以用到Spring的生命周期函数。

在服务创建的时候写一个init函数,加上注解@PostConstruct之后,就会在应用启动的时候调用。

这样一旦数据没有在缓存,就会二次写入。

整个过程用mermaid表示如下:

graph TD A[应用启动] --> B[调用init函数] B --> C{数据在缓存中吗?} C -- 是 --> D[使用缓存数据] C -- 否 --> E[从数据库加载数据] E --> F[写入缓存] F --> D

生命周期函数有哪些使用场景

Spring框架的生命周期回调函数有多种使用场景,以下是一些常见的情况:

  • 初始化资源:在Bean初始化之后,可能需要进行一些资源的初始化操作,比如建立数据库连接、加载配置信息等。通过初始化回调函数,可以在Bean准备就绪后执行这些操作。
  • 释放资源:在Bean销毁之前,可能需要进行一些资源的释放操作,比如关闭数据库连接、释放文件句柄等。通过销毁回调函数,可以在Bean即将被销毁时执行这些清理操作。
  • 依赖注入后的处理:有时候在依赖注入完成后需要执行特定的逻辑,例如根据依赖的情况进行一些动态调整或者校验。
  • 与外部系统集成:在与外部系统集成时,可能需要在Bean创建后或销毁前执行一些初始化或清理工作,例如注册到消息队列、向外部服务发送初始化请求等。
  • 日志记录:使用生命周期回调函数可以方便地记录Bean的创建、初始化和销毁等生命周期事件,以便进行调试和排查问题。
  • 定时任务:通过生命周期回调函数可以实现定时任务的启动和关闭,例如在应用启动时启动定时任务,在应用关闭时停止定时任务。

有哪些生命周期回调

默认的回调函数有如下几种:

  • 初始化回调:在Bean对象实例化后、属性注入完成之后,执行特定的初始化操作的过程。
  • 销毁回调:在Bean对象即将被销毁前执行特定的清理操作的过程。
  • 启动和停止回调:在整个Spring应用程序上下文启动和停止时执行的回调方法。

除此之外还可以通过实现接口BeanPostProcessor来完成任意的回调函数。

初始化回调

在Spring中,Bean的初始化回调可以通过实现InitializingBean接口、@PostConstruct注解或在XML配置中使用init-method来实现。下面将详细说明各种方式的用法,并举例说明。

实现InitializingBean接口

  • 实现InitializingBean接口的类需要实现afterPropertiesSet()方法,在该方法中编写初始化逻辑。
  • 示例代码如下:
java 复制代码
@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注解标记一个方法作为初始化方法,在依赖注入完成后会自动调用该方法。
  • 把上面的代码稍微改造下,示例代码如下:
java 复制代码
@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配置示例:
xml 复制代码
<bean id="myBean" class="com.example.MyBean" init-method="init">
</bean>
java 复制代码
public class MyBean {

    public void init() {
        // 初始化逻辑
        System.out.println("MyBean is being initialized.");
    }
}

源码分析

Spring的调用链路很长,按顺序执行的方法如下:

  • AbstractAutowireCapableBeanFactory#createBean
  • AbstractAutowireCapableBeanFactory#doCreateBeanAbstractAutowireCapableBeanFactory#doCreateBean
  • AbstractAutowireCapableBeanFactory#initializeBean
  • AbstractAutowireCapableBeanFactory#invokeInitMethods

doCreateBean 调用了两个核心函数,其中第二个就是初始化函数。

java 复制代码
// 给bean的属性设置一些逻辑
populateBean(beanName, mbd, instanceWrapper);
// 初始化逻辑,这块就是执行初始化回调的地方
exposedObject = initializeBean(beanName, exposedObject, mbd);

其中初始化的核心代码就是这段。

java 复制代码
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代码如下:

java 复制代码
/**
 * 服务代码
 */
@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配置如下:

java 复制代码
/**
 * 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工厂。

java 复制代码
/**
 * 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片段如下:

java 复制代码
/**
 * 销毁指定名称的 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 接口:

java 复制代码
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的探索与实践,持续迭代中。

欢迎关注或者点个收藏~

相关推荐
计算机-秋大田1 分钟前
基于微信小程序的电子竞技信息交流平台设计与实现(LW+源码+讲解)
spring boot·后端·微信小程序·小程序·课程设计
customer083 小时前
【开源免费】基于SpringBoot+Vue.JS景区民宿预约系统(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
带刺的坐椅5 小时前
无耳科技 Solon v3.0.7 发布(2025农历新年版)
java·spring·mvc·solon·aop
精通HelloWorld!8 小时前
使用HttpClient和HttpRequest发送HTTP请求
java·spring boot·网络协议·spring·http
LUCIAZZZ9 小时前
基于Docker以KRaft模式快速部署Kafka
java·运维·spring·docker·容器·kafka
拾忆,想起9 小时前
如何选择Spring AOP的动态代理?JDK与CGLIB的适用场景
spring boot·后端·spring·spring cloud·微服务
鱼骨不是鱼翅10 小时前
Spring Web MVC基础第一篇
前端·spring·mvc
customer0811 小时前
【开源免费】基于SpringBoot+Vue.JS美食推荐商城(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
一 乐11 小时前
基于微信小程序的酒店管理系统设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·微信小程序·酒店管理系统
hong_zc12 小时前
Spring MVC (三) —— 实战演练
java·spring·mvc