什么是 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();
            }
        });
    }
}

总结

常用场景:

  1. ApplicationReadyEvent​ - 应用完全就绪后执行(最常用)

  2. ContextRefreshedEvent​ - 上下文刷新后执行

  3. ApplicationStartedEvent​ - 应用启动后执行

  4. 自定义事件​ - 业务事件处理

选择建议:

  • 启动后初始化 → ApplicationReadyEvent

  • Bean 加载后处理 → ContextRefreshedEvent

  • 异步任务启动 → ApplicationStartedEvent

  • 业务逻辑 → 自定义事件

注意事项:

  1. 避免在监听器中执行阻塞操作

  2. 注意事件发布顺序

  3. 合理使用 @Order控制执行顺序

  4. 考虑异步处理耗时任务