详解 Spring「启动后」执行
2024-03-28 10:32:53 # Technical # Spring

通常,Spring 应用启动后伴随着都会有些初始化操作,这些初始化操作通常有以下三种方式来实现

  • 实现 ApplicationRunner 接口
  • 实现 CommandLineRunner 接口
  • 实现 ApplicationListener<ContextRefreshedEvent> 接口

ApplicationRunner 接口

方法签名

1
2
3
public interface ApplicationRunner {
void run(ApplicationArguments args) throws Exception;
}

调用时机

ApplicationRunnerrun 方法会在应用启动后,在 ApplicationContext 被成功构建之后执行。这意味着在该时刻,所有的 bean 已经被初始化,并且应用上下文已经准备就绪

使用场景

适用于需要访问命令行参数的初始化逻辑。如果应用启动时需要使用命令行参数,可以使用 ApplicationRunner 来处理

CommandLineRunner 接口

方法签名

1
2
3
public interface CommandLineRunner {
void run(String... args) throws Exception;
}

调用时机

CommandLineRunnerrun 方法也会在应用启动后,但在 ApplicationRunnerrun 方法之前执行

使用场景

适用于简单的初始化逻辑,不需要访问复杂的命令行参数。如果只是需要执行一些简单的初始化工作,可以使用 CommandLineRunner

ApplicationListener<ContextRefreshedEvent> 接口

方法签名

1
2
3
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E var1);
}

调用时机

ApplicationListener<ContextRefreshedEvent> 监听的是 Spring 上下文的刷新事件(ContextRefreshedEvent),即在整个 Spring 上下文初始化完成后触发

使用场景

适用于更广泛的监听场景,不仅仅是应用启动时的初始化。如果需要监听 Spring 上下文刷新事件,无论何时发生,都可以使用该接口

Spring 上下文何时会刷新

Spring 上下文刷新事件(ContextRefreshedEvent)在 Spring 容器初始化或刷新时触发。具体来说,当应用程序上下文被创建并初始化时,ContextRefreshedEvent 事件将被发布

Spring 上下文的初始化和刷新过程包括以下情况:

  1. 应用启动:当应用程序启动时,Spring 容器会初始化并加载配置,创建 bean 实例等。整个容器的初始化过程会触发上下文刷新事件
  2. 容器刷新:当调用 ApplicationContextrefresh() 方法时,将触发容器的刷新过程。这通常在应用程序启动时发生,但也可以在运行时进行
  3. 子容器刷新:如果存在父子容器关系,当子容器初始化并刷新时,也会触发 ContextRefreshedEvent 事件
  4. 使用 ConfigurableApplicationContext 接口的 refresh() 方法:如果应用程序使用的是 ConfigurableApplicationContext 接口,并直接调用了 refresh() 方法,将会触发上下文刷新事件

具体来说,ContextRefreshedEvent 事件的触发点是在 AbstractApplicationContext 类中的 finishRefresh() 方法中。在这个方法中,会发布 ContextRefreshedEvent 事件,通知所有注册的监听器(实现了 ApplicationListener 接口),以便它们可以执行与上下文刷新相关的初始化逻辑

1
2
3
4
5
6
7
8
9
10
11
12
protected void finishRefresh() {
// Initialize other post-refresh beans
initLifecycleProcessor();

// Propagate refresh to lifecycle processor first
getLifecycleProcessor().onRefresh();

// Publish the final event
publishEvent(new ContextRefreshedEvent(this));

// ...
}

所以,如果你想在 Spring 容器刷新完成后执行一些特定的初始化逻辑,可以使用 ApplicationListener 接口,实现 onApplicationEvent 方法,并在该方法中添加你的逻辑

需要刷新 Spring 上下文的场景

在 Spring 中,刷新 Spring 上下文(refresh() 方法)是一个显式的操作,通常是由 Spring 容器的用户(开发者)触发的。下面是一些常见的情况下可能需要手动刷新 Spring 上下文的场景:

  1. 启动时刷新: 在应用启动时,Spring 容器会自动执行初始化和刷新操作。这是最常见的场景,当应用启动时,容器会加载配置文件、初始化 bean,然后执行刷新操作

    1
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  2. 动态刷新: 在运行时需要对 Spring 容器进行动态的更新,例如,修改了配置文件,希望容器能够重新加载配置并应用更改

    1
    2
    3
    ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 执行一些修改配置的操作
    context.refresh();
  3. Web 应用中的刷新: 在 Web 应用中,有时候可能需要手动刷新 Spring WebApplicationContext。这可以通过实现 ServletContextListener 接口,在 contextInitialized 方法中调用 refresh() 来实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class MyContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
    WebApplicationContext context = ...
    ((ConfigurableApplicationContext) context).refresh();
    }

    // ...
    }

通常情况下,不需要手动调用 refresh() 方法。Spring 容器会在初始化时自动刷新。手动调用 refresh() 的情况通常是在特殊的需求场景下,例如:动态修改配置并希望立即生效的情况。在普通的应用开发中,大多数情况下是由 Spring 容器自动执行刷新操作

三者差异

  • ApplicationRunnerrun 方法接收的参数是 ApplicationArgumentsApplicationArguments 提供了对命令行参数的更高级别的访问,包括解析和处理命令行参数。如果需要访问更丰富的命令行参数信息,可以选择使用 ApplicationRunner
  • CommandLineRunnerrun 方法接收的参数是可变长度的字符串数组。如果只是进行一些简单的初始化工作而不需要复杂的命令行参数,可以选择使用 CommandLineRunner
  • ApplicationListener 不仅仅是应用启动时的初始化,只要 Spring 上下文刷新事件发生,都会调用接口