Spring Boot 3之后@PostConstruct 和 @PreDestroy 的替代方案

在 Spring Boot 中, @PostConstruct 和 @PreDestroy 是由 Jakarta EE(前身为 Java EE)提供的生命周期注解,具体来自 jakarta.annotation 包。这些注解不是 Spring Boot 本身的一部分,但由 Spring 的生命周期管理支持。

@PostConstruct 是什么?

@PostConstruct 是 Spring Boot 中的一个注解,告诉 Spring 在创建并注入依赖后但在使用 bean 之前运行特定的方法。

何时使用?

用于在您的应用程序开始处理请求之前初始化一些数据或执行设置任务时。

示例:无 @PostConstruct (问题)

想象你有一个 UserService 从数据库中加载用户

@Service
public class UserService {
    private final UserRepository userRepository;
    private Map<Long, User> userCache = new HashMap<>();

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User getUserById(Long id) {
        return userCache.get(id); // Returns null if cache is empty
    }
}

问题:当应用程序启动时, userCache 为空。

解决方案 @PostConstruct

为了将用户预加载到缓存中,我们使用 @PostConstruct

import javax.annotation.PostConstruct;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;

@Service
public class UserService {
    private final UserRepository userRepository;
    private Map<Long, User> userCache = new HashMap<>();

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @PostConstruct
    public void init() {
        System.out.println("Loading users into cache...");
        userRepository.findAll().forEach(user -> userCache.put(user.getId(), user));
        System.out.println("Users loaded successfully!");
    }

    public User getUserById(Long id) {
        return userCache.get(id); // Now it works because cache is preloaded
    }
}

如何工作?

  1. 1. Spring Boot 创建了 UserService 实例。
  2. 2. 它将 UserRepository 注入到 UserService 中。
  3. 3. 该方法 @PostConstruct 自动运行,将用户加载到缓存中。
  4. 4. 现在, getUserById(id) 可以从缓存中返回用户,而不是空映射

需要注意的是,虽然从 Java 9+开始, @PostConstruct 已被从 javax.annotation 包中移除,但它作为 Jakarta EE 迁移的一部分被移动到了 jakarta.annotation 包中。这意味着 @PostConstruct 和 @PreDestroy 在 Spring Boot 3+中仍然可用,只是在 jakarta.annotation 命名空间下。因此,它们仍然可以用于初始化和清理任务,但现在与 Jakarta EE 标准保持一致。作为替代方案,您可以使用 Spring 的 @EventListener 与 ApplicationReadyEvent 来实现类似的功能

什么是 @EventListener(ApplicationReadyEvent.class) ?

  • • 这是一个 Spring 特有的在应用程序完全启动并准备好处理请求后运行代码的方式。
  • • 不同于 @PostConstruct ,它在初始化单个 bean 之后运行, @EventListener(ApplicationReadyEvent.class) 在整个 Spring 应用程序上下文完全初始化后运行

为什么使用 @EventListener(ApplicationReadyEvent.class) ?

  • • 它确保您的初始化逻辑仅在应用程序完全准备就绪后运行。
  • • 它适用于以下任务:
  • • 加载数据到数据库中。
  • • 启动后台进程。
  • • 验证配置。
  • • 发送启动通知

使用 @EventListener(ApplicationReadyEvent.class)

这里是如何替换 @PostConstruct 为 @EventListener(ApplicationReadyEvent.class)

import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class MyStartupTask {

    // This method will run when the application is fully started
    @EventListener(ApplicationReadyEvent.class)
    public void onApplicationReady() {
        System.out.println("Application is fully started and ready!");
        performStartupTasks();
    }

    private void performStartupTasks() {
        System.out.println("Performing startup tasks...");
        // Example: Load data, validate configurations, etc.
    }
}

启动时加载数据示例

import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class DataLoader {

    @EventListener(ApplicationReadyEvent.class)
    public void loadData() {
        System.out.println("Loading initial data into the database...");
        // Logic to load data
    }
}

发送启动通知

import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class StartupNotifier {

    @EventListener(ApplicationReadyEvent.class)
    public void notifyStartup() {
        System.out.println("Application is ready! Sending notification...");
        // Logic to send a notification (e.g., email, Slack message)
    }
}

2. Spring Boot 中的 @PreDestroy 是什么?

在 Spring Boot 中, @PreDestroy 用于标记一个方法,该方法应在 bean(对象)从 Spring 容器中移除之前运行,通常在应用程序关闭或 bean 不再需要时。

您可以将它视为一种清理方法,在应用程序完全关闭之前执行必要的任务,如关闭文件、释放资源或停止后台进程。

真实世界示例:

想象你有一个咖啡机,使用后需要自动清洗。清洗过程必须在关闭机器之前完成,以免留下咖啡渣或水。这个清理过程就在你关机之前进行。

在 Spring Boot 中, @PreDestroy 类似于确保在关闭应用程序之前正确清理资源的清理过程。

代码示例:

假设我们有一个在应用程序启动时打开文件的服务,我们希望在应用程序关闭前关闭该文件

import javax.annotation.PreDestroy;
import org.springframework.stereotype.Service;

@Service
public class FileService {

    // This simulates opening a file
    private String fileResource = "File opened";

    // @PreDestroy method to clean up the file resource before the bean is destroyed
    @PreDestroy
    public void cleanUp() {
        System.out.println("Cleaning up the resources before shutdown...");
        // Simulating closing the file or resource
        fileResource = null;
    }
}

这里发生什么:

  • • 当应用程序启动时, FileService 实例被创建,并打开文件(这只是一个模拟)。
  • • 当应用程序关闭时,会调用 @PreDestroy 方法 cleanUp() ,该方法关闭文件(或在我们的情况下,使资源无效)。
  • • 这确保了当应用程序停止时,我们不会留下任何打开的资源

我认为你可能有所疑问,但这里 @PostConstruct 已被弃用。从 Java 9+ 开始,以 javax.annotation 开头的包为什么 @PreDestroy 没有被弃用?

您完全正确,关于 Spring Boot 3+中移除 javax.annotation.PostConstruct ,这是由于 javax.annotation 包在 Java 9 中被弃用所导致的后果。然而,由于一些重要原因, @PreDestroy 在 Spring Boot 3+中仍然可用:

为什么在 Spring Boot 3+中 @PreDestroy 仍然可用

  1. 1. 向后兼容性:Spring 被设计为与现有应用程序保持向后兼容。虽然由于依赖于 javax.annotation (在 Java 9+ 中已删除)而不再推荐使用 @PostConstruct ,但 @PreDestroy 目前仍然可以使用。它尚未被弃用或删除,因为:
  •  它不依赖于任何特殊的外部包来实现其功能。
  •  它仍然被认为对许多应用很有用,尤其是在执行清理任务,如关闭资源或关闭线程时。
  1. jakarta EE 迁移:从 Java 9 开始, javax.annotation 包被移动到jakarta  EE。 @PreDestroy 注解现在是jakarta注解包的一部分,这是基于 Java EE 应用程序(如 Spring)注解的未来标准。
  2. 在 Spring 3+中,虽然 javax.annotation 已被移除,但 Spring 支持 Jakarta EE 注解(如 @PreDestroy ),并正在向它们过渡。Spring 6(与 Jakarta EE 对齐)更专注于采用 Jakarta 的注解和库,这就是为什么 @PreDestroy 仍然可以在 Spring Boot 3+中无问题使用。

Spring Boot 3+ 中 @PostConstruct 和 @PreDestroy 的替代方案

如果您针对 Spring Boot 3+,并且由于 Jakarta 过渡而无法使用 @PostConstruct 或 @PreDestroy 注解,可以使用 Spring 自己的事件监听机制。

您可以使用 Spring 的 @EventListener 配合 ApplicationReadyEvent 来触发初始化逻辑

import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

@Service
public class MyService {

    @EventListener(ApplicationReadyEvent.class)
    public void init() {
        System.out.println("Application is ready. Performing initialization...");
    }
}

 @PreDestroy 替代方案:

如果您想替换 @PreDestroy 用于清理逻辑,可以实现 DisposableBean 接口或使用 @Bean(destroyMethod = "methodName") 方法。

1. 使用 DisposableBean :

import org.springframework.beans.factory.DisposableBean;
import org.springframework.stereotype.Service;

@Service
public class MyService implements DisposableBean {

    @Override
    public void destroy() throws Exception {
        System.out.println("Cleaning up resources before shutdown...");
    }
}

2. 使用 @Bean(destroyMethod = "methodName")

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean(destroyMethod = "close")
    public MyService myService() {
        return new MyService();
    }
}

class MyService {

    public void close() {
        System.out.println("Cleaning up resources...");
    }
}

总结:

• @PreDestroy 在 Spring Boot 3+ 中仍然可用,因为它在资源清理方面仍然有用,并且功能由 Jakarta EE 注解支持。

• @PostConstruct 在 Spring Boot 3+ 中已移除,因为 Java 9+ 中已弃用并移除了 javax.annotation ,建议使用 Spring 的替代方案如 @EventListener(ApplicationReadyEvent.class)

 

请登录后发表评论

    没有回复内容