[SpringBoot] Several ways to execute the specified code when the project starts

Spring dynamically loads (refreshes) configuration files

Preface

When the Spring application service starts, it needs to load some data and perform some initialization work in advance. For example: deleting temporary files, clearing cached information, reading configuration files, and obtaining database connections. These tasks only need to be performed once after booting.

0. ApplicationListener

A successful container refresh means that all Bean initialization has been completed. After the container is refreshed, Spring will call ApplicationListener<ContextRefreshedEvent>the onApplicationEventmethods of all implemented Beans in the container , and the application can achieve 监听容器初始化完成事件this goal.

@Component
@Slf4j
public class StartupApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
    
    

    public static int counter;

    @Override public void onApplicationEvent(ContextRefreshedEvent event) {
    
    
        log.info("Increment counter");
        counter++;
    }
}

Error-prone points
Need extra attention when using web containers. In web projects (such as spring mvc), there will be two containers in the system, one is root application contextand the other is our own context(as a sub-container of the root application context). If you follow the above wording, it will cause onApplicationEvent方法被执行两次. The solution to this problem is as follows:

@Component
@Slf4j
public class StartupApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
    
    

    public static int counter;

    @Override public void onApplicationEvent(ContextRefreshedEvent event) {
    
    
        if (event.getApplicationContext().getParent() == null) {
    
    
            // root application context 没有parent
            LOG.info("Increment counter");
            counter++;
        }
    }
}

High-level gameplay : With custom events, you can use Spring to implement an observer mode at minimal cost:

Customize an event:

@Data
public class NotifyEvent extends ApplicationEvent {
    
    
    private String email;
    private String content;
    public NotifyEvent(Object source) {
    
    
        super(source);
    }
    public NotifyEvent(Object source, String email, String content) {
    
    
        super(source);
        this.email = email;
        this.content = content;
    }
}

Register an event listener

@Component
public class NotifyListener implements ApplicationListener<NotifyEvent> {
    
    
    @Override
    public void onApplicationEvent(NotifyEvent event) {
    
    
        System.out.println("邮件地址:" + event.getEmail());
        System.out.println("邮件内容:" + event.getContent());
    }
}

Post an event

@RunWith(SpringRunner.class)
@SpringBootTest
public class ListenerTest {
    
    
    @Autowired
    private WebApplicationContext webApplicationContext;

    @Test
    public void testListener() {
    
    
        NotifyEvent event = new NotifyEvent("object", "[email protected]", "This is the content");
        webApplicationContext.publishEvent(event);
    }
}

Execute the unit test and you can see that the address and content of the email are printed out

1.ServletContextAware interface

Implement the ServletContextAware interface to override the setServletContext method

  • The class that implements the interface can直接获取Web应用的ServletContext实例(Servler容器)
@Component
public class ServletContextAwareImpl implements ServletContextAware {
    
    
    /**
     * 在填充普通bean属性之后但在初始化之前调用
     * 类似于initializing Bean的afterpropertiesset或自定义init方法的回调
     *
     */
    @Override
    public void setServletContext(ServletContext servletContext) {
    
    
        System.out.println("===============ServletContextAwareImpl初始化================");
    }
}

2.ServletContextListener interface

Implement the ServletContextListener interface to override the contextInitialized method

  • It can 监听 ServletContext 对象的生命周期, that is, monitor the life cycle of a Web application.
  • When the Servlet container start or stop Web application, it will trigger ServletContextEvent 事件the events generated by ServletContextListenerthe two methods two treatments:
    • contextInitialized(ServletContextEvent sce) : This method is called when the Servlet container starts the Web application. After calling this method, the container initializes the Filter and initializes the servlets that need to be initialized when the Web application starts.
    • contextDestroyed(ServletContextEvent sce) : This method is called when the Servlet container terminates the Web application. Before calling this method, the container will destroy all Servlet and Filter filters.
@Component
public class ServletContextListenerImpl implements ServletContextListener {
    
    
    @Override
    public void contextInitialized(ServletContextEvent sce) {
    
    
        //ServletContext servletContext = sce.getServletContext();
        System.out.println("===============ServletContextListenerImpl初始化(contextInitialized)================");
    }
    
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
    
    
        System.out.println("===============ServletContextListenerImpl(contextDestroyed)================");
    }
}

3.@PostConstruct annotation

  • The method modified by @PostConstruct will run when the server loads the Servlet, and 在构造函数之后,init()方法之前运行.
  • The order of initialization operations using @PostConstruct annotations is 最快的, the premise is these operations 不依赖其它Bean的初始化完成.
@Component
public class PostConstructInit {
    
    
    //静态代码块会在依赖注入后自动执行,并优先执行
    static{
    
    
        System.out.println("---static--");
    }
    /**
     *  @Postcontruct’在依赖注入完成后自动调用
     */
    @PostConstruct
    public static void haha(){
    
    
        System.out.println("===============@Postcontruct初始化(在依赖注入完成后自动调用)================");
    }
}

@PreDestroy
@PreDestroy modified method will run when the server uninstalls the Servlet, and will only be called once by the server, similar to the destroy() method of Servlet. The method modified by @PreDestroy will run after the destroy() method, before the servlet is completely uninstalled.

4.CommandLineRunner interface

Implement the CommandLineRunner interface to override the run method

  • 字符串数组Command line parameters that can be received
  • in构造SpringApplication实例完成之后调用
  • You can use Order注解or use Ordered接口to specify the calling order, in @Order()值越小,优先级越高
  • Use ApplicationRunner and CommandLineRunner at the same time,默认情况下ApplicationRunner比CommandLineRunner先执行
@Component
public class CommandLineRunnerImpl implements CommandLineRunner {
    
    
    /**
     * 用于指示bean包含在SpringApplication中时应运行的接口。可以在同一应用程序上下文中定义多个commandlinerunner bean,并且可以使用有序接口或@order注释对其进行排序。
     * 如果需要访问applicationArguments而不是原始字符串数组,请考虑使用applicationRunner。
     *
     */
    @Override
    public void run(String... args) throws Exception {
    
    
        System.out.println("===============CommandLineRunnerImpl初始化================");
    }
}

5. ApplicationRunner interface

Implement ApplicationRunner interface to rewrite run method

  • ApplicationRunner is usingApplicationArguments来获取参数的
  • in构造SpringApplication实例完成之后调用run()的时候调用
  • Can be specified by @Orderannotation or use , inOrdered接口调用顺序@Order()值越小,优先级越高
  • Use ApplicationRunner and CommandLineRunner at the same time,默认情况下ApplicationRunner比CommandLineRunner先执行
  • ApplicationRunner and CommandLineRunner have the same functions, the only difference is mainly reflected in the processing of parameters,ApplicationRunner 可以接收更多类型的参数
@Component
public class ApplicationRunnerImpl implements ApplicationRunner {
    
    
    /**
     * 用于指示bean包含在SpringApplication中时应运行的接口。可以定义多个applicationRunner Bean
     * 在同一应用程序上下文中,可以使用有序接口或@order注释对其进行排序。
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
    
    
        System.out.println("===============ApplicationRunnerImpl初始化================");
    }
}
  • args.getSourceArgs(): Get all the parameters in the command line.
  • args.getNonOptionArgs(): Get the keyless parameter in the command line (same as CommandLineRunner).
  • args.getOptionNames(): Get the keys of all key/value parameters.
  • args.getOptionValues(key)): Get the value of the parameter in the form of key/value according to the key.

6. InitializingBean interface

After all the properties of the object are initialized, the afterPropertiesSet() method will be called

public class InitializingBeanImpl implements InitializingBean {
    
    
    @Override
    public void afterPropertiesSet() {
    
    
        System.out.println("===============InitializingBeanImpl初始化================");
    }
}

7.SmartLifecycle interface

SmartLifecycle can not only 初始化后execute one logic, but also 关闭前execute another logic, and can also control the execution order of multiple SmartLifecycles, just like this class name indicates, this is one 智能的生命周期管理接口.


import org.springframework.context.SmartLifecycle;
import org.springframework.stereotype.Component;

/**
 * SmartLifecycle测试
 *
 * 
 * 
 */
@Component
public class TestSmartLifecycle implements SmartLifecycle {
    
    

    private boolean isRunning = false;

    /**
     * 1. 我们主要在该方法中启动任务或者其他异步服务,比如开启MQ接收消息<br/>
     * 2. 当上下文被刷新(所有对象已被实例化和初始化之后)时,将调用该方法,默认生命周期处理器将检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。
     * 如果为“true”,则该方法会被调用,而不是等待显式调用自己的start()方法。
     */
    @Override
    public void start() {
    
    
        System.out.println("start");

        // 执行完其他业务后,可以修改 isRunning = true
        isRunning = true;
    }

    /**
     * 如果项目中有多个实现接口SmartLifecycle的类,则这些类的start的执行顺序按getPhase方法返回值从小到大执行。默认为 0<br/>
     * 例如:1比2先执行,-1比0先执行。 stop方法的执行顺序则相反,getPhase返回值较大类的stop方法先被调用,小的后被调用。
     */
    @Override
    public int getPhase() {
    
    
        // 默认为0
        return 0;
    }

    /**
     * 根据该方法的返回值决定是否执行start方法。默认为 false<br/> 
     * 返回true时start方法会被自动执行,返回false则不会。
     */
    @Override
    public boolean isAutoStartup() {
    
    
        // 默认为false
        return true;
    }

    /**
     * 1. 只有该方法返回false时,start方法才会被执行。    默认返回 false <br/>
     * 2. 只有该方法返回true时,stop(Runnable callback)或stop()方法才会被执行。
     */
    @Override
    public boolean isRunning() {
    
    
        // 默认返回false
        return isRunning;
    }

    /**
     * SmartLifecycle子类的才有的方法,当isRunning方法返回true时,该方法才会被调用。
     */
    @Override
    public void stop(Runnable callback) {
    
    
        System.out.println("stop(Runnable)");

        // 如果你让isRunning返回true,需要执行stop这个方法,那么就不要忘记调用callback.run()。
        // 否则在你程序退出时,Spring的DefaultLifecycleProcessor会认为你这个TestSmartLifecycle没有stop完成,程序会一直卡着结束不了,等待一定时间(默认超时时间30秒)后才会自动结束。
        // PS:如果你想修改这个默认超时时间,可以按下面思路做,当然下面代码是springmvc配置文件形式的参考,在SpringBoot中自然不是配置xml来完成,这里只是提供一种思路。
        // <bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
        //      <!-- timeout value in milliseconds -->
        //      <property name="timeoutPerShutdownPhase" value="10000"/>
        // </bean>
        callback.run();

        isRunning = false;
    }

    /**
     * 接口Lifecycle的子类的方法,只有非SmartLifecycle的子类才会执行该方法。<br/>
     * 1. 该方法只对直接实现接口Lifecycle的类才起作用,对实现SmartLifecycle接口的类无效。<br/>
     * 2. 方法stop()和方法stop(Runnable callback)的区别只在于,后者是SmartLifecycle子类的专属。
     */
    @Override
    public void stop() {
    
    
        System.out.println("stop");

        isRunning = false;
    }

}
  • start() : After the bean is initialized, this method will be executed.
  • stop() : After the container is closed, the spring container finds that the current object implements SmartLifecycle and calls stop(Runnable). If it only implements Lifecycle, it calls stop().
  • isRunning() : The current state, used to judge whether your broken component is running.
  • getPhase() : Controls the callback sequence of multiple SmartLifecycles . The smaller the return value, the earlier the start() method is executed, and the later the stop() method is executed.
  • isAutoStartup() : Look at the return value of this method before the start method is executed. If false is returned, the start method will not be executed.
  • stop(Runnable) : After the container is closed, the spring container finds that the current object implements SmartLifecycle, and it calls stop(Runnable). If it only implements Lifecycle, it calls stop().

8. Control the Bean initialization sequence

If you want to specify the order in which the Spring container loads Beans, you can achieve it through implementation org.springframework.core.Ordered接口or use org.springframework.core.annotation.Order注解.

  • The smaller the order value, the earlier the initialization class will be executed

8.1. Implement the Ordered interface

/**
 * Created by pangkunkun on 2017/9/3.
 */
@Component
public class MyApplicationRunner implements ApplicationRunner,Ordered{
    
    


    @Override
    public int getOrder(){
    
    
        return 1;//通过设置这里的数字来知道指定顺序
    }

    @Override
    public void run(ApplicationArguments var1) throws Exception{
    
    
        System.out.println("MyApplicationRunner1!");
    }
}

8.2. Use @Order annotation

@Component
@Order(value = 1)
public class MyApplicationRunner implements ApplicationRunner{
    
    

    @Override
    public void run(ApplicationArguments var1) throws Exception{
    
    
        System.out.println("MyApplicationRunner1!");
    }

Original initialization method

If you do not depend on the implementation of Spring, we can 静态代码块, in 类构造函数achieving appropriate logic

  • The order of initialization of Java classes is in order静态变量 > 静态代码块 > 全局变量 > 初始化代码块 > 构造方法 .

Spring event mechanism
Spring event monitoring mechanism
Spring Events

Guess you like

Origin blog.csdn.net/qq877728715/article/details/110860306