[SpringBoot] Varias formas de ejecutar el código especificado cuando se inicia el proyecto

Spring carga (actualiza) dinámicamente archivos de configuración

Prefacio

Cuando se inicia el servicio de la aplicación Spring, necesita cargar algunos datos y realizar un trabajo de inicialización por adelantado. Por ejemplo: eliminar archivos temporales, borrar la información almacenada en caché, leer archivos de configuración y obtener conexiones a la base de datos Estas tareas solo deben realizarse una vez después del arranque.

0. ApplicationListener

Una actualización exitosa del contenedor significa que se ha completado toda la inicialización de Bean. Después de que se actualice el contenedor, Spring llamará a todos los métodos ApplicationListener<ContextRefreshedEvent>Bean implementados onApplicationEventen el contenedor , y la aplicación puede lograr 监听容器初始化完成事件este objetivo.

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

    public static int counter;

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

Puntos propensos a errores
Necesitan especial atención cuando se utilizan contenedores web. En proyectos web (como Spring MVC), habrá dos contenedores en el sistema, uno es root application contexty el otro es el nuestro context(como subcontenedor del contexto de la aplicación raíz ). Si sigue la redacción anterior, causará onApplicationEvent方法被执行两次. La solución a este problema es la siguiente:

@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++;
        }
    }
}

Jugabilidad de alto nivel : con eventos personalizados, puedes usar Spring para implementar un modo de observador a un costo mínimo:

Personaliza un evento:

@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;
    }
}

Registrar un detector de eventos

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

Publica un evento

@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);
    }
}

Ejecute la prueba unitaria y podrá ver que la dirección y el contenido del correo electrónico están impresos

Interfaz 1.ServletContextAware

Implementar la interfaz ServletContextAware para anular el método setServletContext

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

Interfaz 2.ServletContextListener

Implementar la interfaz ServletContextListener para anular el método contextInitialized

  • Es decir 监听 ServletContext 对象的生命周期, puede monitorear el ciclo de vida de una aplicación web.
  • Cuando el contenedor de Servlet inicia o detiene la aplicación web, activará ServletContextEvent 事件los eventos generados por ServletContextListenerlos dos métodos dos tratamientos:
    • contextInitialized (ServletContextEvent sce) : este método se llama cuando el contenedor de Servlet inicia la aplicación web. Después de llamar a este método, el contenedor inicializa el filtro e inicializa los servlets que deben inicializarse cuando se inicia la aplicación web.
    • contextDestroyed (ServletContextEvent sce) : este método se llama cuando el contenedor Servlet termina la aplicación web. Antes de llamar a este método, el contenedor destruirá todos los filtros Servlet y Filter.
@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. @ Anotación PostConstruct

  • El método modificado por @PostConstruct se ejecutará cuando el servidor cargue el Servlet y 在构造函数之后,init()方法之前运行.
  • El orden de las operaciones de inicialización usando las anotaciones @PostConstruct es 最快的, la premisa son estas operaciones 不依赖其它Bean的初始化完成.
@Component
public class PostConstructInit {
    
    
    //静态代码块会在依赖注入后自动执行,并优先执行
    static{
    
    
        System.out.println("---static--");
    }
    /**
     *  @Postcontruct’在依赖注入完成后自动调用
     */
    @PostConstruct
    public static void haha(){
    
    
        System.out.println("===============@Postcontruct初始化(在依赖注入完成后自动调用)================");
    }
}

@PreDestroy
El método modificado de @PreDestroy se ejecutará cuando el servidor desinstale el Servlet, y el servidor solo lo llamará una vez, similar al método destroy () de Servlet. El método modificado por @PreDestroy se ejecutará después del método destroy (), antes de que el servlet se desinstale por completo.

4.Interfaz CommandLineRunner

Implementar la interfaz CommandLineRunner para anular el método de ejecución

  • 字符串数组Parámetros de la línea de comando que se pueden recibir
  • en构造SpringApplication实例完成之后调用
  • Puede usar Order注解o usar Ordered接口para especificar el orden de llamada, en @Order ()值越小,优先级越高
  • Utilice ApplicationRunner y CommandLineRunner al mismo tiempo,默认情况下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. Interfaz ApplicationRunner

Implementar la interfaz ApplicationRunner para reescribir el método de ejecución

  • ApplicationRunner está usandoApplicationArguments来获取参数的
  • en构造SpringApplication实例完成之后调用run()的时候调用
  • Puede especificarse mediante @Orderanotación o uso , enOrdered接口调用顺序@Order()值越小,优先级越高
  • Utilice ApplicationRunner y CommandLineRunner al mismo tiempo,默认情况下ApplicationRunner比CommandLineRunner先执行
  • ApplicationRunner y CommandLineRunner tienen las mismas funciones, la única diferencia se refleja principalmente en el procesamiento de parámetros,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 (): Obtiene todos los parámetros en la línea de comando.
  • args.getNonOptionArgs (): Obtiene el parámetro sin llave en la línea de comando (igual que CommandLineRunner).
  • args.getOptionNames (): Obtiene las claves de todos los parámetros clave / valor.
  • args.getOptionValues ​​(clave)): Obtiene el valor del parámetro en forma de clave / valor de acuerdo con la clave.

6. Interfaz InitializingBean

Después de que se inicialicen todas las propiedades del objeto, se llamará al método afterPropertiesSet ()

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

7.Interfaz SmartLifecycle

SmartLifecycle no solo puede 初始化后ejecutar una lógica, sino también 关闭前ejecutar otra lógica, y también puede controlar el orden de ejecución de múltiples SmartLifecycles, tal como lo indica este nombre de clase, esta es una 智能的生命周期管理接口.


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 () : Después de inicializar el bean, se ejecutará este método.
  • stop () : Después de cerrar el contenedor, el contenedor de primavera encuentra que el objeto actual implementa SmartLifecycle y llama a stop (Runnable). Si solo implementa Lifecycle, llama a stop ().
  • isRunning () : el estado actual, utilizado para juzgar si su componente roto se está ejecutando.
  • getPhase () : controla la secuencia de devolución de llamada de múltiples SmartLifecycles . Cuanto menor sea el valor de retorno, antes se ejecutará el método start () y más tarde se ejecutará el método stop ().
  • isAutoStartup () : Mira el valor de retorno de este método antes de que se ejecute el método de inicio. Si se devuelve falso, el método de inicio no se ejecutará.
  • stop (Runnable) : Después de cerrar el contenedor, el contenedor de resorte encuentra que el objeto actual implementa SmartLifecycle y llama a stop (Runnable). Si solo implementa Lifecycle, llama a stop ().

8. Controle la secuencia de inicialización de Bean

Si desea especificar el orden en el que el contenedor Spring carga los Beans, puede lograrlo mediante la implementación org.springframework.core.Ordered接口o el uso org.springframework.core.annotation.Order注解.

  • Cuanto menor sea el valor de la orden, antes se ejecutará la clase de inicialización

8.1. Implementar la interfaz ordenada

/**
 * 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. Usar la anotación @Order

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

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

Método de inicialización original

Si no depende de la implementación de Spring, podemos 静态代码块, para 类构造函数lograr la lógica adecuada

  • El orden de inicialización de las clases de Java está en orden静态变量 > 静态代码块 > 全局变量 > 初始化代码块 > 构造方法 .

Mecanismo de eventos de primavera Mecanismo de
monitoreo de
eventos de primavera Eventos de primavera

Supongo que te gusta

Origin blog.csdn.net/qq877728715/article/details/110860306
Recomendado
Clasificación