springboot服务启动时,自动触发任务
作为浅谈就不深挖底层源码,具体源码可详见其他博客,都有贴出源码的具体分析,本文仅作为个人的学习心得分享。
1.从bean下手
①afterPropertiesSet
afterPropertiesSet()需要实现InitializingBean接口,到此实例化完成,进入生存期。
示例:
@Component
public class CacheTask implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("执行输出b" + "---" + new Date());
}
}
②postProcessBeforeInitialization与postProcessAfterInitialization
postProcessBeforeInitialization和postProcessAfterInitialization这两个方法皆为BeanPostProcessor接口下的方法。
postProcessBeforeInitialization和postProcessAfterInitialization这两个方法分别是在Spring容器中的bean初始化(bean属性配置完成)前后执行,所以Spring容器中的每个bean对象初始化前后,都会执行BeanPostProcessor接口的两个方法。
示例:
@Component
public class BeanTest implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("执行输出A" + "---" + new Date());
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("执行输出B" + "---" + new Date());
return bean;
}
}
③@PostConstruct注解
@PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法只会被服务器执行一次。
spring的Bean在创建的时候会进行初始化,而初始化过程会解析出@PostConstruct注解的方法,并反射调用该方法。从而,在启动的时候该方法被执行了。
spring中Constructor、@Autowired、@PostConstruct的顺序
Constructor >> @Autowired >> @PostConstruct
注意:子类实例化过程中会调用父类中的@PostConstruct方法!
示例:
@Component
public class CacheTask {
@PostConstruct
public void CacheTaskInit() {
System.out.println("执行输出A" + "---" + new Date());
}
}
④init-method
init-method在springMVC时期需要在web.xml文件里进行配置,而springboot项目中就是用约定的注解来替换配置。
示例:
@Component
public class CacheTask {
public void initMethod() {
System.out.println("initMethod");
}
}
另还需添加一个配置类
@Configuration
public class BeanConfig {
@Bean(initMethod = "initMethod")
public CacheTask cacheTask(){
return new CacheTask ();
}
}
相同点:@PostConstruct和afterPropertiesSet都是在bean的属性注入完毕之后才执行,都可以用来进行bean 的初始化
区别:@PostConstruct是通过BeanPostProcessor的前置方法中执行的(我们也可以参考这个自定义注解实现),略早于通过继承InitializingBean的afterPropertiesSet初始化方法。而afterPropertiesSet早于Init-Method。
postProcessBeforeInitialization与postProcessAfterInitialization是每个bean初始化(bean属性配置完成)即InitializingBean前后执行的,因此对于整个服务来说有多少个bean便会执行多少次。
只需执行一次的任务建议使用@PostConstruct或afterPropertiesSet。
更容易理解的话就是@PostConstruct和afterPropertiesSet只对你所交给spring容器托管的这个类进行bean初始化的时候调用,而postProcessBeforeInitialization与postProcessAfterInitialization是对每个bean进行负责。
2.从spring容器下手
①ApplicationListener
总的来说,是监听spring容器的事件来实现,类似于上文从bean入手,就是在其某阶段监听到该事件进行任务的触发。
示例:
ApplicationStartedEvent
//监听ApplicationStarted
@Component
public class StartListener implements ApplicationListener<ApplicationStartedEvent> {
@Override
public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent) {
System.out.println("started listener");
}
}
ApplicationReadyEvent
@Component
public class ReadyListener implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
System.out.println("Ready Listener");
}
}
ContextRefreshedEvent
ContextRefreshedEvent:ApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext接口中使用 refresh() 方法来发生。此处的初始化是指:所有的Bean被成功装载,后处理Bean被检测并激活,所有Singleton Bean 被预实例化,ApplicationContext容器已就绪可用
@Component
public class ContextRefreshedListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
System.out.println("ContextRefreshed Listener");
}
}
②CommandLineRunner
示例:
@Component
public class CommandLineRunnerTest implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("执行CommandLineRunner方法");
}
}
③ApplicationRunner
示例:
@Component
public class ApplicationRunnerTest implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("执行ApplicationRunner方法");
}
}
为了理清上述方案的运行顺序,我们须知Spring boot运行时,会发送以下事件
-
ApplicationStartingEvent
-
ApplicationEnvironmentPreparedEvent:当Environment已经准备好,在context 创建前
-
ApplicationContextInitializedEvent:在ApplicationContext 创建和ApplicationContextInitializer都被调用后,但是bean definition没有被加载前
-
ApplicationPreparedEvent:bean definition已经加载,但是没有refresh
-
ApplicationStartedEvent: context 已经被refresh, 但是application 和command-line 的runner都没有被调用
-
AvailabilityChangeEvent
-
ApplicationReadyEvent: application 和command-line 的runner都被调用后触发
-
AvailabilityChangeEvent
-
ApplicationFailedEvent: 启动失败触发
总结:在ApplicationPreparedEvent之后和ApplicationStartedEvent之前发送ContextRefreshedEvent
ContextRefreshedEvent可能多次执行因为web应用会出现父子容器,这样就会触发两次
CommandLineRunner、ApplicationRunner 接口是在容器启动成功(设置并刷新ApplicationContext)后的最后一步回调,可以通过@Order注解(属性指定数字越小表示优先级越高)或者Ordered接口来控制执行顺序,如果 Order 相同,那么 ApplicationRunner 先执行。
这两个接口都以相同的方式工作,并提供一个单独的运行方法,在springapplication.run(.)完成之前,它将被调用。ApplicationRunner 和 CommandLineRunner 是 Spring Boot 提供的专门用于处理启动后的初始化工作的接口,他们的执行一定是在容器启动的最后一步。也就是 run 方法的最后一步。
CommandLineRunner接口提供对应用程序参数的访问,作为一个简单的字符串数组,ApplicationRunner提供了ApplicationArguments。用更容易理解的话来说,两者参数都为Spring Boot 入口的传参,即为springboot启动类main函数内的args参数。CommandLineRunner的参数是最原始的参数,没有做任何处理。ApplicationRunner的参数是ApplicationArguments,是对原始参数做了进一步的封装,ApplicationArguments对象可以解析–name=value形式。
执行顺序在SpringApplication启动之前,在大部分监听器之后。
3.从ServletContext下手
ServletContext,是一个全局的储存信息的空间,服务器开始,其就存在,服务器关闭,其才释放。
①setServletContext
示例:
@Component
public class ServletContextTest implements ServletContextAware {
/**
* 在填充普通bean属性之后但在初始化之前调用
* 由此可见比@PostConstruct与afterPropertiesSet调用都要早
* 类似于initializingbean的afterpropertiesset或自定义init方法的回调
*
*/
@Override
public void setServletContext(ServletContext servletContext) {
System.out.println("调用了setServletContext");
}
}
②contextInitialized
类似于上文中的监听spring容器的事件,只不过这个方法只是监听ServletContext初始化的事件
示例:
@Component
public class ServletContextListenerTest implements ServletContextListener {
/**
*监听ServletContext初始化事件
*/
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("setServletContextInit Listener");
}
}
setServletContext在contextInitialized之前调用。注:contextInitialized在@PostConstruct之前调用
从程序进程角度来说,很明显setServletContext在ServletContext初始化之前。
注:上类方法都需要@Component等注解交给spring容器进行管理配置
若文中有误,欢迎大家指出并进行评判与斧正。