1 、使用接口ApplicationRunner和CommandLineRunner
这两个接口都是在容器运行后执行的,如下图示
如果项目需要在系统启动时,初始化资源,可以继承这两个接口,实现诸如缓存预热、DB连接等。
实现ApplicationRunner接口
@Component public class MyApplicationRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { System.out.println("缓存预热~"); System.out.println("DB连接~"); System.out.println("日志系统初始化~"); System.out.println("资源文件初始化~"); System.out.println("常驻后台线程的资源初始化~"); } }
实现CommandLineRunner接口
@Component public class MyCommandLineRunner implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("缓存预热~"); System.out.println("DB连接~"); System.out.println("日志系统初始化~"); System.out.println("资源文件初始化~"); System.out.println("常驻后台线程的资源初始化~"); } }
2、监听spring事件ApplicationListener<>
ApplicationContext事件的实现机制是观察者模式,通过两个接口ApplicationEvent和ApplicationListener来实现。
spring容器在初始化时,会在不同的阶段,发布ApplicationEvent事件,因此,可以通过ApplicationListener监听各个阶段的事件来实现不同的功能。
spring提供了以下ApplicationEvent事件:
ContextStartedEvent 上下文启动事件
此Start事件是当Spring容器启动时发布,即调用start() 方法时执行,意味着所有Lifecyc Bean 都监听到了 start 事件。
ContextRefreshedEvent 上下文刷新事件
此 Refreshed 事件是当容器实例化时发布,即执行 refresh() 方法,此时所有的 Bean 都已加载,后置处理器被激活,容器中所有的对象都可以使用,如 果容器支持热重载,则 refresh 可以被触发多次(XmlWebApplicatonContext支持热刷新,而GenericApplicationContext则不支持)。
ContextStoppedEvent 上下文停止事件
当使用ConfigurableApplicationContext 接口中的 stop() 停止 ApplicationContext 时,发布这个事件。你可以在接受到这个事件后做必要的清理的工作。
ContextClosedEvent 上下文关闭事件
当使用ConfigurableApplicationContext接口中的close() 方法关闭ApplicationContext 时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不 能被刷新或重启。
RequestHandledEvent 请求处理完成事件
此Request handled事件只在使用spring的DispatcherServlet时有效,当一个请求被处理完成时发布。
springboot提供的事件SpringApplicationEvent:
ApplicationStartingEvent SpringApplication启动事件
SpringBoot启动时发布该事件,其发布时机在环境变量Environment或容器ApplicationContext创建前但在注册ApplicationListener具体监听器之后。
ApplicationEnvironmentPreparedEvent 环境准备好事件
ApplicationEnvironmentPreparedEvent事件多了一个environment属性,作用是利用事件发布订阅机制,相应监听器可以从ApplicationEnvironmentPreparedEvent事件中取出environment变量,然后可以为environment属性增加属性值或读出environment变量中的值。像ConfigFileApplicationListener监听器就是监听了ApplicationEnvironmentPreparedEvent事件,然后取出ApplicationEnvironmentPreparedEvent事件的environment属性,然后再为environment属性增加application.properties配置文件中的环境变量值。
当SpringApplication已经开始启动且环境变量Environment已经创建后,并且为环境变量Environment配置了命令行和Servlet等类型的环境变量后,此时会发布ApplicationEnvironmentPreparedEvent事件。监听ApplicationEnvironmentPreparedEvent事件的第一个监听器是ConfigFileApplicationListener,因为是ConfigFileApplicationListener监听器还要为环境变量Environment增加application.properties配置文件中的环境变量;此后还有一些也是监听ApplicationEnvironmentPreparedEvent事件的其他监听器监听到此事件时,此时可以说环境变量Environment几乎已经完全准备好了。
ApplicationContextInitializedEvent ApplicationContext容器初始化事件
ApplicationContextInitializedEvent事件多了个ConfigurableApplicationContext类型的context属性,context属性的作用同样是为了相应监听器可以拿到这个context属性执行一些逻辑。
ApplicationContextInitializedEvent事件在ApplicationContext容器创建后,且为ApplicationContext容器设置了environment变量和执行了ApplicationContextInitializers的初始化方法后但在bean定义加载前触发,标志ApplicationContext已经初始化完毕。
ApplicationContextInitializedEvent是在为context容器配置environment变量后触发,此时ApplicationContextInitializedEvent等事件只要有context容器的话,那么其他需要environment环境变量的监听器只需要从context中取出environment变量即可,从而ApplicationContextInitializedEvent等事件没必要再配置environment属性。
ApplicationPreparedEvent ApplicationContext 容器准备好事件
ApplicationPreparedEvent事件多了个ConfigurableApplicationContext类型的context属性,多了context属性的作用是能让监听该事件的监听器能拿到context属性,监听器拿到context属性一般有如下作用:
从事件中取出context属性,然后可以增加一些后置处理器,比如ConfigFileApplicationListener监听器监听到ApplicationPreparedEvent事件后,然后取出context变量,通过context变量增加了PropertySourceOrderingPostProcessor这个后置处理器;
通过context属性取出beanFactory容器,然后注册一些bean,比如LoggingApplicationListener监听器通过ApplicationPreparedEvent事件的context属性取出beanFactory容器,然后注册了springBootLoggingSystem这个单例bean;
通过context属性取出Environment环境变量,然后就可以操作环境变量,比如PropertiesMigrationListener。
ApplicationPreparedEvent事件在ApplicationContext容器已经完全准备好时但在容器刷新前触发,在这个阶段bean定义已经加载完毕还有environment已经准备好可以用了。
ApplicationStartedEvent 应用开始事件
ApplicationStartedEvent事件将在容器刷新后但ApplicationRunner和CommandLineRunner的run方法执行前触发,标志Spring容器已经刷新,此时容器已经准备完毕了。
ApplicationRunner和CommandLineRunner接口有啥作用呢?我们一般会在Spring容器刷新完毕后,此时可能有一些系统参数等静态数据需要加载,此时我们就可以实现了ApplicationRunner或CommandLineRunner接口来实现静态数据的加载。
ApplicationReadyEvent 应用准备好事件
ApplicationReadyEvent事件在调用完ApplicationRunner和CommandLineRunner的run方法后触发,此时标志SpringApplication已经正在运行。
ApplicationFailedEvent 容器启动失败事件
可以看到ApplicationFailedEvent事件除了多了一个context属性外,还多了一个Throwable类型的exception属性用来记录SpringBoot启动失败时的异常。
ApplicationFailedEvent事件在SpringBoot启动失败时触发,标志SpringBoot启动失败。
基于以上事件类型,我们可以监听ContextRefreshedEvent事件,初始化系统资源,以及监听ContextStoppedEvent事件来清理资源。
初始化资源:
@Component public class MyApplicationListener implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent event) { System.out.println("做一些初始化的工作,比如缓存预热~"); ApplicationContext ct= event.getApplicationContext(); System.out.println(ct); } }
清理资源:
@Component public class MyApplicationStopListener implements ApplicationListener<ContextStoppedEvent> { @Override public void onApplicationEvent(ContextStoppedEvent event) { System.out.println("做一些清理资源的工作,比如缓存清理,DB连接清理~"); ApplicationContext ct= event.getApplicationContext(); System.out.println(ct); } @Override public boolean supportsAsyncExecution() { return ApplicationListener.super.supportsAsyncExecution(); } }
3、@PostConstruct、@PreDestroy注解
@PostConstruct注解时针对Bean初始化完成后,要执行的方法,@PreDestroy注解时Bean销毁时执行。
@Component public class RedisInit { @PostConstruct public void init(){ System.out.println("缓存预热~"); } @PreDestroy public void destroy(){ System.out.println("缓存清理~"); } }
4、InitializingBean、DisposableBean接口
InitializingBean、DisposableBean 接口也是针对的Bean的。
@Component public class RedisInitializingBean implements InitializingBean, DisposableBean { @Override public void afterPropertiesSet() throws Exception { System.out.println("缓存预热~"); } @Override public void destroy() throws Exception { System.out.println("缓存清理~"); } }
5、配置Bean时,指定Bean的init和destroy方法
@Bean(name = "redisInit",initMethod = "init",destroyMethod = "") public RedisInit redisInit(){ return new RedisInit(); }
6、SmartLifecycle接口
SmartLifecycle接口有三个方法start stop isRuning,可以在start方法里初始化资源,在stop方法里实现资源清理
@Component public class RedisSmartLifecycle implements SmartLifecycle { private boolean running = false; @Override public void start() { System.out.println("缓存预热~"); running=true; } @Override public void stop() { System.out.println("缓存清理~"); running=false; } @Override public boolean isRunning() { return running; } }