【问题标题】:Spring Boot 启动后运行代码
【发布时间】:2015-02-08 22:12:24
【问题描述】:

我想在我的 spring-boot 应用程序开始监视目录的更改后运行代码。

我已尝试运行一个新线程,但此时尚未设置 @Autowired 服务。

我已经能够找到ApplicationPreparedEvent,它在设置@Autowired 注释之前触发。理想情况下,我希望在应用程序准备好处理 http 请求后触发事件。

spring-boot 中运行应用程序后,是否有更好的事件可以使用,或者运行代码的更好方式?

【问题讨论】:

标签: java spring spring-boot


【解决方案1】:

试试:

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application extends SpringBootServletInitializer {

    @SuppressWarnings("resource")
    public static void main(final String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);

        context.getBean(Table.class).fillWithTestdata(); // <-- here
    }
}

【讨论】:

  • 当您将应用程序作为 war 文件部署到外部 tomcat 时,这不起作用。它仅适用于嵌入式 tomcat
  • 不,它不起作用。但在这个用例中,我喜欢更明确的方式而不是@Component。请参阅@cjstehno 的答案以使其在战争文件中工作。
【解决方案2】:

为什么不创建一个在初始化时启动监视器的 bean,例如:

@Component
public class Monitor {
    @Autowired private SomeService service

    @PostConstruct
    public void init(){
        // start your monitoring in here
    }
}

在对 bean 进行任何自动装配之前,不会调用 init 方法。

【讨论】:

  • 有时@PostConstruct 触发得太早了。例如,当使用 Spring Cloud Stream Kafka 时,@PostConstruct 在应用程序绑定到 Kafka 之前触发。 Dave Syer 的解决方案更好,因为它触发及时。
  • @PostConstruct 发生在初始化期间,而不是之后。虽然这在某些情况下很有用,但如果您想在 Spring Boot 启动后运行,这不是正确的答案。例如,虽然@PostConstruct 没有完成,但没有一个端点可用。
  • 不完全符合 OP 问题。在@PostConstruct,这个bean是被构造的,就像这个类范围内的bean是自动装配的,等等。但是整个应用程序可能还没有准备好,例如,其他bean可能还在注入或装配的过程中。
  • 如果您在 @PostConstruct 中执行任何长时间运行的操作(例如带有重试回退的 HTTP 请求),您将不会得到很好的行为,因为发送 SIGTERM 不会中断运行 @987654328 的线程@,因此您的应用程序拒绝关闭,直到该方法退出。
【解决方案3】:

“Spring Boot”方式是使用CommandLineRunner。只需添加那种类型的豆子,你就可以走了。在 Spring 4.1 (Boot 1.2) 中,还有一个 SmartInitializingBean,它在所有内容初始化后都会收到回调。还有SmartLifecycle(来自 Spring 3)。

【讨论】:

  • 有什么例子吗?是否可以在应用程序运行后通过命令行在任意时刻执行 bean?
  • 不知道你所说的“任意时刻”是什么意思。 Spring Boot 用户指南和示例提供了使用 CommandLineRunner(以及更新的 ApplicationRunner)的示例:docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/…
  • 我发现,Lifecycle 是在应用程序的启动/停止阶段执行异步任务的首选选项,我试图发现 CommandLineRunner 和 InitializingBeans 之间的其他差异,但不能找到任何相关的信息。
  • 夫妇通常使用example code CommandLineRunner
  • 如果您不需要访问命令行参数,为什么它比@EventListener(ApplicationReadyEvent.class) 更好?
【解决方案4】:

您尝试过 ApplicationReadyEvent 吗?

@Component
public class ApplicationStartup 
implements ApplicationListener<ApplicationReadyEvent> {

  /**
   * This event is executed as late as conceivably possible to indicate that 
   * the application is ready to service requests.
   */
  @Override
  public void onApplicationEvent(final ApplicationReadyEvent event) {

    // here your code ...

    return;
  }
}

代码来自:http://blog.netgloo.com/2014/11/13/run-code-at-spring-boot-startup/

这是documentation 提到的关于启动事件的内容:

...

应用程序运行时按以下顺序发送应用程序事件:

ApplicationStartedEvent 在运行开始时发送,但在此之前 除了监听器和初始化器的注册之外的任何处理。

ApplicationEnvironmentPreparedEvent 在上下文中使用的环境已知时发送,但在上下文之前 已创建。

ApplicationPreparedEvent 在刷新开始之前发送,但在加载 bean 定义之后。

在刷新后发送 ApplicationReadyEvent 并处理任何相关的回调以指示应用程序已准备好 服务请求。

如果启动时出现异常,则发送 ApplicationFailedEvent。

...

【讨论】:

  • 作为替代方案,您可以在 Bean 方法上使用 @EventListenerannotation 来执行此操作,将要挂钩的类事件作为参数传递。
  • 这在 spring-boot 2 中发生了变化。如果您从 1.x 移植并使用 ApplicationStartedEvent,那么您现在需要 ApplicationStartingEvent。
【解决方案5】:

您可以使用 ApplicationRunner 扩展一个类,覆盖 run() 方法并在那里添加代码。

import org.springframework.boot.ApplicationRunner;

@Component
public class ServerInitializer implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments applicationArguments) throws Exception {

        //code goes here

    }
}

【讨论】:

  • 完美的 Spring Boot。但是当类有 ApplicationScope 时,run() 方法被调用了两次。所以上面的 PostConstruct 方法效果更好。
【解决方案6】:

为 Dave Syer 的回答提供了一个例子,这就像一个魅力:

@Component
public class CommandLineAppStartupRunner implements CommandLineRunner {
    private static final Logger logger = LoggerFactory.getLogger(CommandLineAppStartupRunner.class);

    @Override
    public void run(String...args) throws Exception {
        logger.info("Application started with command-line arguments: {} . \n To kill this application, press Ctrl + C.", Arrays.toString(args));
    }
}

【讨论】:

    【解决方案7】:

    在春季使用SmartInitializingSingleton bean > 4.1

    @Bean
    public SmartInitializingSingleton importProcessor() {
        return () -> {
            doStuff();
        };
    
    }
    

    作为替代方案,可以实现 CommandLineRunner bean 或使用 @PostConstruct 注释 bean 方法。

    【讨论】:

    • 我可以在该方法中要求一个 Autowired 依赖吗?我想设置个人资料
    【解决方案8】:

    就这么简单:

    @EventListener(ApplicationReadyEvent.class)
    public void doSomethingAfterStartup() {
        System.out.println("hello world, I have just started up");
    }
    

    测试版本1.5.1.RELEASE

    【讨论】:

    • 谢谢。这使我的代码无需任何更改即可工作。再次感谢您提供如此简单的答案。这也适用于@RequestMapping 注解,没有任何问题。
    • 有人可能还希望使用@EventListener(ContextRefreshedEvent.class) 来代替,它会在创建 bean 之后、但在服务器启动之前触发。它可用于在任何请求到达服务器之前执行活动。
    • 放置事件监听器的这个类是否需要用Component、Service等注解?
    • 在 Spring boot 2.0.5.RELEASE 上测试
    • 在 2.2.2 版本上测试。它完美地工作。这个解决方案节省了我的时间。
    【解决方案9】:

    ApplicationReadyEvent 仅在您要执行的任务不是正确服务器操作的要求时才真正有用。启动一个异步任务来监控某些东西的变化就是一个很好的例子。

    但是,如果您的服务器在任务完成之前处于“未就绪”状态,那么最好实现 SmartInitializingSingleton,因为您将在 REST 端口打开之前获得回调并且您的服务器已开始营业。

    不要试图将@PostConstruct 用于应该只发生一次的任务。当您注意到它被多次调用时,您会感到非常惊讶......

    【讨论】:

    • 这应该是选择的答案。正如@Andy 指出的那样,SmartInitializingSingleton 在端口打开之前被调用。
    【解决方案10】:

    只需为 Spring Boot 应用程序实现 CommandLineRunner。 你需要实现run方法,

    public classs SpringBootApplication implements CommandLineRunner{
    
        @Override
            public void run(String... arg0) throws Exception {
            // write your logic here 
    
            }
    }
    

    【讨论】:

      【解决方案11】:

      试试这个,它会在应用程序上下文完全启动时运行您的代码。

       @Component
      public class OnStartServer implements ApplicationListener<ContextRefreshedEvent> {
      
          @Override
          public void onApplicationEvent(ContextRefreshedEvent arg0) {
                      // EXECUTE YOUR CODE HERE 
          }
      }
      

      【讨论】:

      【解决方案12】:

      我真的很喜欢@cahen (https://stackoverflow.com/a/44923402/9122660) 关于使用EventListener 注释的建议,因为它非常干净。不幸的是,我无法在 Spring + Kotlin 设置中使用它。对 Kotlin 有用的是将类添加为方法参数:

      @EventListener 
      fun doSomethingAfterStartup(event: ApplicationReadyEvent) {
          System.out.println("hello world, I have just started up");
      }
      

      【讨论】:

      • 放到spring boot应用类中不要随意出边@SpringBootApplication class MyApplication { @EventListener(ApplicationReadyEvent::class) fun doSomethingAfterStartup() { println("hello world, I have just started up") } }
      • 你不需要把它放到@SpringBootApplication 类中。任何配置类都可以
      【解决方案13】:

      在 Spring Boot 应用程序启动后执行代码块的最佳方法是使用 PostConstruct 注释。或者您也可以使用命令行运行器。

      1.使用 PostConstruct 注解

      @Configuration
      public class InitialDataConfiguration {
      
          @PostConstruct
          public void postConstruct() {
              System.out.println("Started after Spring boot application !");
          }
      
      }
      

      2。使用命令行运行器 bean

      @Configuration
      public class InitialDataConfiguration {
      
          @Bean
          CommandLineRunner runner() {
              return args -> {
                  System.out.println("CommandLineRunner running in the UnsplashApplication class...");
              };
          }
      }
      

      【讨论】:

      • 我相信@PostConstruct 方法是bean 初始化的一部分。我已经看到由于 @PostConstruct 方法失败而导致依赖不满足而导致 ApplicationContext 加载失败。
      【解决方案14】:

      使用 CommandLineRunner 或 ApplicationRunner 的最佳方式 唯一的区别是 run() 方法 CommandLineRunner 接受字符串数组,ApplicationRunner 接受 ApplicationArugument。

      【讨论】:

        【解决方案15】:

        你可以使用@Component

        @RequiredArgsConstructor
        @Component
        @Slf4j
        public class BeerLoader implements CommandLineRunner {
            //declare 
        
            @Override
            public void run(String... args) throws Exception {
                //some code here 
        
            }
        

        【讨论】:

          【解决方案16】:

          您有多种选择:

          使用 CommandLineRunnerApplicationRunner 作为 Bean 定义:

          Spring Boot 在应用程序启动过程结束时执行这些操作。在大多数情况下,CommandLineRunner 将完成这项工作。以下是使用 Java 8 实现 CommandLineRunner 的示例:

          @Bean
          public CommandLineRunner commandLineRunner() {
             return (args) -> System.out.println("Hello World");
          }
          

          请注意,args 是 String 参数数组。您还可以提供此接口的实现并将其定义为 Spring 组件:

          @Component
          public class MyCommandLineRunner implements CommandLineRunner {
          
              @Override
              public void run(String... args) throws Exception {
                  System.out.println("Hello World");
              }
          }
          

          如果您需要更好的参数管理,可以使用ApplicationRunner。 ApplicationRunner 采用具有增强的参数管理选项的ApplicationArguments 实例。

          您还可以使用 Spring 的 @Order 注释订购 CommandLineRunnerApplicationRunner bean:

           @Bean
           @Order(1)
           public CommandLineRunner commandLineRunner() {
              return (args) -> System.out.println("Hello World, Order 1");
           }
          
           @Bean
           @Order(2)
           public CommandLineRunner commandLineRunner() {
              return (args) -> System.out.println("Hello World, Order 2");
           }
          

          使用 Spring Boot 的 ContextRefreshedEvent:

          Spring Boot 在启动时会发布几个事件。这些事件表明应用程序启动过程中某个阶段的完成。你可以收听ContextRefreshedEvent并执行自定义代码:

          @EventListener(ContextRefreshedEvent.class)
          public void execute() {
              if(alreadyDone) {
                return;
              }
              System.out.println("hello world");
          } 
          

          ContextRefreshedEvent 被多次发布。因此,请确保检查代码执行是否已经完成。

          【讨论】:

            【解决方案17】:

            Spring boot 提供了一个 ApplicationRunner 接口,该接口带有一个在应用程序启动时调用的 run() 方法。 然而,我们有一个 ApplicationArguments 类的实例,而不是传递给回调方法的原始字符串参数。

            @Component
                public class AppStartupRunner implements ApplicationRunner {
            
                @Override
                public void run(ApplicationArguments args) throws Exception {
                    //some logic here
                }
            }
            

            【讨论】:

            猜你喜欢
            • 2017-09-28
            • 2017-05-24
            • 2018-04-22
            • 2020-03-01
            • 2018-11-28
            • 1970-01-01
            • 2012-04-02
            • 1970-01-01
            相关资源
            最近更新 更多