什么是 ApplicationListener?
ApplicationListener是 Spring 框架的事件监听器接口,用于监听 Spring 应用生命周期中的各种事件。
1. 基本使用
1.1 实现 ApplicationListener 接口
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
@Component
public class MyApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("ContextRefreshedEvent 事件触发!");
System.out.println("ApplicationContext: " + event.getApplicationContext());
}
}1.2 使用 @EventListener 注解(推荐)
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class MyEventListener {
@EventListener
public void handleContextRefreshed(ContextRefreshedEvent event) {
System.out.println("@EventListener: ContextRefreshedEvent");
}
}2. Spring Boot 主要事件类型
2.1 应用启动事件序列
以下是 Spring Boot 启动时的事件触发顺序:
@Component
public class AllSpringBootEventsListener implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(ApplicationEvent event) {
// 监听所有事件
System.out.printf("事件: %s%n", event.getClass().getSimpleName());
}
}启动事件顺序:
@Component
public class StartupEventListener {
// 1. ApplicationStartingEvent - 启动开始时
@EventListener
public void onApplicationStarting(ApplicationStartingEvent event) {
System.out.println("1️⃣ ApplicationStartingEvent - 启动开始");
}
// 2. ApplicationEnvironmentPreparedEvent - 环境准备完成
@EventListener
public void onEnvironmentPrepared(ApplicationEnvironmentPreparedEvent event) {
System.out.println("2️⃣ ApplicationEnvironmentPreparedEvent - 环境准备完成");
}
// 3. ApplicationContextInitializedEvent - ApplicationContext 初始化完成
@EventListener
public void onContextInitialized(ApplicationContextInitializedEvent event) {
System.out.println("3️⃣ ApplicationContextInitializedEvent - 上下文初始化完成");
}
// 4. ApplicationPreparedEvent - Bean 定义加载完成,但未刷新
@EventListener
public void onApplicationPrepared(ApplicationPreparedEvent event) {
System.out.println("4️⃣ ApplicationPreparedEvent - 应用准备完成");
}
// 5. ContextRefreshedEvent - ApplicationContext 刷新完成
@EventListener
public void onContextRefreshed(ContextRefreshedEvent event) {
System.out.println("5️⃣ ContextRefreshedEvent - 上下文刷新完成");
}
// 6. ApplicationStartedEvent - 应用已启动
@EventListener
public void onApplicationStarted(ApplicationStartedEvent event) {
System.out.println("6️⃣ ApplicationStartedEvent - 应用已启动");
}
// 7. AvailabilityChangeEvent - 应用状态变更
@EventListener
public void onAvailabilityChange(AvailabilityChangeEvent<ReadinessState> event) {
System.out.println("7️⃣ AvailabilityChangeEvent - 应用状态: " + event.getState());
}
// 8. ApplicationReadyEvent - 应用完全就绪
@EventListener
public void onApplicationReady(ApplicationReadyEvent event) {
System.out.println("8️⃣ ApplicationReadyEvent - 应用完全就绪 ✅");
}
// 9. ApplicationFailedEvent - 启动失败
@EventListener
public void onApplicationFailed(ApplicationFailedEvent event) {
System.out.println("❌ ApplicationFailedEvent - 启动失败");
}
}3. 核心事件详解
3.1 ApplicationReadyEvent(最常用)
@Component
@Slf4j
public class ApplicationReadyListener {
/**
* 应用完全启动后执行
* 适合:启动后初始化、资源检查、预热缓存等
*/
@EventListener(ApplicationReadyEvent.class)
public void onApplicationReady(ApplicationReadyEvent event) {
log.info("应用启动完成,开始执行初始化任务...");
// 获取 ApplicationContext
ApplicationContext context = event.getApplicationContext();
// 1. 打印启动信息
printStartupInfo(event);
// 2. 初始化数据
initData();
// 3. 预热缓存
warmUpCache();
// 4. 健康检查
healthCheck();
log.info("初始化任务执行完成!");
}
private void printStartupInfo(ApplicationReadyEvent event) {
ConfigurableEnvironment env = event.getApplicationContext().getEnvironment();
String appName = env.getProperty("spring.application.name", "未知应用");
String port = env.getProperty("server.port", "8080");
String[] profiles = env.getActiveProfiles();
if (profiles.length == 0) {
profiles = env.getDefaultProfiles();
}
log.info("应用名称: {}", appName);
log.info("运行端口: {}", port);
log.info("激活环境: {}", String.join(",", profiles));
log.info("Java版本: {}", System.getProperty("java.version"));
}
}3.2 ContextRefreshedEvent
@Component
public class ContextRefreshedListener {
/**
* ApplicationContext 初始化或刷新后触发
* 可以访问所有 Bean,但可能某些服务还未完全就绪
*/
@EventListener(ContextRefreshedEvent.class)
public void onContextRefreshed(ContextRefreshedEvent event) {
System.out.println("=== ContextRefreshedEvent ===");
ApplicationContext context = event.getApplicationContext();
// 获取所有 Bean 名称
String[] beanNames = context.getBeanDefinitionNames();
System.out.printf("Bean 总数: %d%n", beanNames.length);
// 检查特定 Bean
if (context.containsBean("dataSource")) {
System.out.println("✅ 数据源 Bean 已加载");
}
}
}3.3 ApplicationStartedEvent
@Component
public class ApplicationStartedListener {
/**
* 应用已启动,但可能某些服务还未完全就绪
* 适合:启动异步任务
*/
@EventListener(ApplicationStartedEvent.class)
public void onApplicationStarted(ApplicationStartedEvent event) {
System.out.println("=== ApplicationStartedEvent ===");
// 启动后台任务
startBackgroundTask();
}
private void startBackgroundTask() {
Thread.startVirtualThread(() -> {
System.out.println("后台任务开始执行...");
// 执行一些初始化任务
});
}
}4. 高级用法
4.1 条件监听
@Component
public class ConditionalEventListener {
// 1. 基于 SpEL 表达式
@EventListener(condition = "#event.applicationContext.getBean('environment').getActiveProfiles()[0] == 'dev'")
public void onDevEnvironment(ContextRefreshedEvent event) {
System.out.println("开发环境初始化...");
}
// 2. 基于配置属性
@EventListener(condition = "#{environment.getProperty('app.feature.enabled') == 'true'}")
public void onFeatureEnabled(ContextRefreshedEvent event) {
System.out.println("功能模块启用...");
}
// 3. 监听多种事件
@EventListener({ContextRefreshedEvent.class, ApplicationReadyEvent.class})
public void handleMultipleEvents(ApplicationEvent event) {
System.out.println("事件: " + event.getClass().getSimpleName());
}
}4.2 异步监听
@Component
public class AsyncEventListener {
// 异步执行监听器
@Async
@EventListener
public void onApplicationReadyAsync(ApplicationReadyEvent event) {
System.out.println("异步处理启动完成事件,当前线程: " + Thread.currentThread().getName());
// 执行耗时操作
performHeavyInitialization();
}
// 确保 Spring 支持异步
@Configuration
@EnableAsync
public static class AsyncConfig {
}
}4.3 事件发布
@Service
@Slf4j
public class UserService {
private final ApplicationEventPublisher eventPublisher;
public UserService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public User createUser(CreateUserRequest request) {
// 创建用户
User user = new User(request.getUsername());
// 发布自定义事件
eventPublisher.publishEvent(new UserCreatedEvent(this, user));
return user;
}
}
// 自定义事件
public class UserCreatedEvent extends ApplicationEvent {
private final User user;
public UserCreatedEvent(Object source, User user) {
super(source);
this.user = user;
}
public User getUser() {
return user;
}
}
// 监听自定义事件
@Component
public class UserEventListener {
@EventListener
public void onUserCreated(UserCreatedEvent event) {
log.info("用户创建事件: {}", event.getUser().getUsername());
// 发送欢迎邮件
sendWelcomeEmail(event.getUser());
// 记录审计日志
logAudit(event.getUser());
}
}5. 实用示例
5.1 启动 Banner 显示
@Component
@Slf4j
@Profile("!prod") // 非生产环境显示
public class StartupBanner implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
ConfigurableEnvironment env = event.getApplicationContext().getEnvironment();
String appName = env.getProperty("spring.application.name", "Application");
String port = env.getProperty("server.port", "8080");
String contextPath = env.getProperty("server.servlet.context-path", "");
String banner = """
╔═══════════════════════════════════════════════════════╗
║ 🚀 启动成功! ║
╠═══════════════════════════════════════════════════════╣
║ 应用: %s ║
║ 地址: http://localhost:%s%s ║
║ 环境: %s ║
║ 时间: %s ║
╚═══════════════════════════════════════════════════════╝
""".formatted(
appName,
port,
contextPath,
String.join(",", env.getActiveProfiles()),
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
);
System.out.println(banner);
}
}5.2 启动时数据初始化
@Component
@ConditionalOnProperty(name = "app.data.init.enabled", havingValue = "true")
@Slf4j
public class DataInitializer implements ApplicationListener<ApplicationReadyEvent> {
private final UserRepository userRepository;
private final RoleRepository roleRepository;
public DataInitializer(UserRepository userRepository, RoleRepository roleRepository) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
}
@Override
@Transactional
public void onApplicationEvent(ApplicationReadyEvent event) {
log.info("开始初始化基础数据...");
// 1. 初始化角色
initRoles();
// 2. 初始化管理员用户
initAdminUser();
// 3. 初始化系统配置
initSystemConfig();
log.info("基础数据初始化完成!");
}
private void initRoles() {
if (roleRepository.count() == 0) {
List<Role> roles = List.of(
new Role("ADMIN", "系统管理员"),
new Role("USER", "普通用户"),
new Role("GUEST", "访客")
);
roleRepository.saveAll(roles);
log.info("初始化了 {} 个角色", roles.size());
}
}
}5.3 健康检查和预热
@Component
@Slf4j
public class SystemHealthChecker implements ApplicationListener<ApplicationReadyEvent> {
private final DataSource dataSource;
private final RedisTemplate<String, String> redisTemplate;
private final RestTemplate restTemplate;
public SystemHealthChecker(DataSource dataSource,
RedisTemplate<String, String> redisTemplate,
RestTemplate restTemplate) {
this.dataSource = dataSource;
this.redisTemplate = redisTemplate;
this.restTemplate = restTemplate;
}
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
log.info("开始系统健康检查...");
// 检查数据库连接
checkDatabaseConnection();
// 检查 Redis 连接
checkRedisConnection();
// 检查外部服务
checkExternalServices();
// 预热缓存
warmUpCaches();
log.info("系统健康检查完成!");
}
private void checkDatabaseConnection() {
try (Connection conn = dataSource.getConnection()) {
try (Statement stmt = conn.createStatement()) {
stmt.execute("SELECT 1");
log.info("✅ 数据库连接正常");
}
} catch (SQLException e) {
log.error("❌ 数据库连接失败", e);
}
}
private void warmUpCaches() {
Thread.startVirtualThread(() -> {
log.info("开始预热缓存...");
// 预热常用数据
warmUpFrequentData();
log.info("缓存预热完成");
});
}
}6. 最佳实践
6.1 执行顺序控制
@Component
@Order(Ordered.HIGHEST_PRECEDENCE) // 最高优先级
public class HighPriorityListener implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
System.out.println("最先执行");
}
}
@Component
@Order(Ordered.LOWEST_PRECEDENCE) // 最低优先级
public class LowPriorityListener implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
System.out.println("最后执行");
}
}6.2 避免的陷阱
@Component
public class EventListenerPitfalls {
// ❌ 错误:在 ContextRefreshedEvent 中发布新事件可能导致循环
@EventListener(ContextRefreshedEvent.class)
public void badPractice(ContextRefreshedEvent event) {
// 避免在这里发布可能触发相同事件的事件
}
// ✅ 正确:在 ApplicationReadyEvent 中执行初始化
@EventListener(ApplicationReadyEvent.class)
public void goodPractice(ApplicationReadyEvent event) {
// 所有 Bean 都完全就绪
}
// ❌ 错误:执行耗时操作阻塞主线程
@EventListener(ApplicationReadyEvent.class)
public void blockingOperation(ApplicationReadyEvent event) {
// 长时间运行的任务会延迟启动完成
heavySyncTask(); // 避免!
}
// ✅ 正确:使用异步
@Async
@EventListener(ApplicationReadyEvent.class)
public void asyncOperation(ApplicationReadyEvent event) {
heavyAsyncTask(); // 好!
}
}6.3 测试监听器
@SpringBootTest
class ApplicationReadyListenerTest {
@Autowired
private ApplicationContext context;
@MockBean
private SomeService someService;
@Test
void testApplicationReadyListener() {
// 手动发布事件
context.publishEvent(new ApplicationReadyEvent(
new SpringApplication(),
new String[0],
context
));
// 验证监听器行为
verify(someService, times(1)).someMethod();
}
}7. 完整示例
package com.example.demo.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.stereotype.Component;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 应用启动完成监听器
* 显示启动信息、健康检查、初始化等
*/
@Component
@Slf4j
@Order // 默认顺序
public class ComprehensiveStartupListener implements ApplicationListener<ApplicationReadyEvent> {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static long startupStartTime;
static {
startupStartTime = System.currentTimeMillis();
}
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
long startupEndTime = System.currentTimeMillis();
long startupDuration = startupEndTime - startupStartTime;
ConfigurableEnvironment env = event.getApplicationContext().getEnvironment();
try {
// 1. 显示启动信息
displayStartupBanner(env, startupDuration);
// 2. 执行健康检查
performHealthChecks(event);
// 3. 异步初始化
asyncInitialization(event);
} catch (Exception e) {
log.error("启动监听器执行失败", e);
}
}
private void displayStartupBanner(ConfigurableEnvironment env, long duration) throws UnknownHostException {
String appName = env.getProperty("spring.application.name", "Spring Boot Application");
String port = env.getProperty("server.port", "8080");
String contextPath = env.getProperty("server.servlet.context-path", "");
String[] activeProfiles = env.getActiveProfiles();
if (activeProfiles.length == 0) {
activeProfiles = new String[]{"default"};
}
String hostAddress = InetAddress.getLocalHost().getHostAddress();
String hostName = InetAddress.getLocalHost().getHostName();
String banner = """
════════════════════════════════════════════════════════════════════════
APPLICATION STARTED SUCCESSFULLY
════════════════════════════════════════════════════════════════════════
📱 Application : %s
🌐 Local URL : http://localhost:%s%s
🌐 External URL: http://%s:%s%s
🏷️ Profiles : %s
⏱️ Startup Time: %.2f seconds
📅 Started At : %s
💻 Host : %s (%s)
☕ Java Version: %s
📦 Spring Boot : %s
════════════════════════════════════════════════════════════════════════
""".formatted(
appName,
port, contextPath,
hostAddress, port, contextPath,
String.join(", ", activeProfiles),
duration / 1000.0,
LocalDateTime.now().format(FORMATTER),
hostName, hostAddress,
System.getProperty("java.version"),
event.getSpringApplication().getClass().getPackage().getImplementationVersion()
);
System.out.println(banner);
log.info("应用启动完成,耗时: {}ms", duration);
}
private void performHealthChecks(ApplicationReadyEvent event) {
log.info("执行启动健康检查...");
// 可以添加数据库、Redis、MQ 等健康检查
}
private void asyncInitialization(ApplicationReadyEvent event) {
// 使用虚拟线程执行异步初始化
Thread.startVirtualThread(() -> {
log.info("开始异步初始化...");
try {
Thread.sleep(Duration.ofSeconds(1));
// 执行初始化任务
log.info("异步初始化完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}总结
常用场景:
ApplicationReadyEvent - 应用完全就绪后执行(最常用)
ContextRefreshedEvent - 上下文刷新后执行
ApplicationStartedEvent - 应用启动后执行
自定义事件 - 业务事件处理
选择建议:
启动后初始化 →
ApplicationReadyEventBean 加载后处理 →
ContextRefreshedEvent异步任务启动 →
ApplicationStartedEvent业务逻辑 → 自定义事件
注意事项:
避免在监听器中执行阻塞操作
注意事件发布顺序
合理使用
@Order控制执行顺序考虑异步处理耗时任务
评论