浅浅了解下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的探索与实践,持续迭代中。

欢迎关注或者点个收藏~

相关推荐
2401_854391082 分钟前
高效开发:SpringBoot网上租赁系统实现细节
java·spring boot·后端
wxin_VXbishe12 分钟前
springboot合肥师范学院实习实训管理系统-计算机毕业设计源码31290
java·spring boot·python·spring·servlet·django·php
OEC小胖胖26 分钟前
Spring Boot + MyBatis 项目中常用注解详解(万字长篇解读)
java·spring boot·后端·spring·mybatis·web
2401_857617621 小时前
SpringBoot校园资料平台:开发与部署指南
java·spring boot·后端
quokka561 小时前
Springboot 整合 logback 日志框架
java·spring boot·logback
计算机学姐1 小时前
基于SpringBoot+Vue的在线投票系统
java·vue.js·spring boot·后端·学习·intellij-idea·mybatis
落落落sss1 小时前
MybatisPlus
android·java·开发语言·spring·tomcat·rabbitmq·mybatis
2401_854391082 小时前
Spring Boot大学生就业招聘系统的开发与部署
java·spring boot·后端
杨荧3 小时前
【JAVA开源】基于Vue和SpringBoot的洗衣店订单管理系统
java·开发语言·vue.js·spring boot·spring cloud·开源
2401_857610033 小时前
SpringBoot实现:校园资料分享平台开发指南
服务器·spring boot·php