当我们在用SpringBoot开发后端服务时,通常我们会有一些需求是需要在服务启动完成后提前运行的,比如:
- 将一些字典项数据从数据库加载到缓存,以方便在服务运行时快速从缓存获取。【调用@Autowired的Bean的方法从数据库获取需要缓存的数据】
- 要执行某些定时任务进行相关的统计与计算。【通过调用服务中带注解@Async的方法或用CompletableFuture类在方法中直接创建多线程实现】
- 启动心跳检测相关的线程。【同第2点】
- 检测是否某项配置已经开启,否则启动失败。【与@Value定义的属性或@ConfigurationProperties定义的类配合使用】
- 同时启动多个异步线程进行处理某些业务。【同第2点】
要达到以上目的,在SpringBoot中有三种方法可以实现,如下所示:
- 实现ApplicationRunner接口【在所有的Bean加载到IOC容器之后,在打印出服务已经启动完成【Started DemoApplication in 1.464 seconds (JVM running for 1.744)】的日志时才会调用所有实现了此接口的Bean中的run方法】
- 实现CommandLineRunner接口 【同上,不过先会调用ApplicationRunner接口的实现类,其次再调用CommandLineRunner接口的实现类】
- 实现ApplicationListener接口 【在所有的Bean加载到IOC容器之后,所有的Runner方法也都运行过后,才会加载ApplicationListener接口的实现类,内部通过发送各种事件的机制进行执行相关的监听器,此处使用ApplicationReadyEvent事件来触发】
SpringBoot启动的过程如下图所示【其中忽略一些细节,只列出主要的几个步聚】:
其中橘黄色部分代表springboot会在适当的时机调用上面三种实现。
上面三种方法实现示例如下所示:
1. 实现ApplicationRunner接口
package com.crh.demo.listener;
import com.crh.demo.service.cache.CacheService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* This runner is used to execute the pre-load functions of the cache service, schedule service,
* heart-check service before all beans has been loaded to Application Context.
*
* @author crh
*/
@Slf4j
@Component
@Order(0)
public class PrepareProgressApplicationRunner implements ApplicationRunner {
@Autowired
private CacheService cacheService;
@Override
public void run(ApplicationArguments args) throws Exception {
log.info("[PPAR]-[run]-[INF]-[IC0001]:[加载所有的缓存数据到内存中.]");
cacheService.loadAllCachedData();
}
}
2. 实现CommandLineRunner接口
package com.crh.demo.listener;
import com.crh.demo.service.cache.CacheService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* This runner is used to execute the pre-load functions of the cache service, schedule service,
* heart-check service before all beans has been loaded to Application Context.
*
* @author crh
*/
@Slf4j
@Component
@Order(1)
public class PrepareProgressCommandRunner implements CommandLineRunner {
@Autowired
private CacheService cacheService;
@Override
public void run(String... args) throws Exception {
log.info("[PPCR]-[run]-[INF]-[IC0001]:[加载所有的缓存数据到内存中.]");
cacheService.loadAllCachedData();
}
}
3. 实现ApplicationListener接口
package com.crh.demo.listener;
import com.crh.demo.service.cache.CacheService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* This listener is used to start the listening functions of the cache service, schedule service,
* heart-check service before all beans has been loaded to Application Context.
*
* @author crh
*/
@Slf4j
@Component
@Order(0)
public class PrepareProcessListener implements ApplicationListener<ApplicationReadyEvent>, Ordered{
@Autowired
private CacheService cacheService;
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
// throw new RuntimeException("test"); 此处主要是为在启动执行预加载服务时出错阻止服务启动成功。
log.info("[PPL]-[onApplicationEvent]-[INF]-[IC0001]:[加载所有的缓存数据到内存中.]");
cacheService.loadAllCachedData();
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
以上三种方法的运行结果如下:
2021-04-07 01:01:56.936 INFO 11940 --- [ main] com.crh.demo.DemoApplication : Started DemoApplication in 1.492 seconds (JVM running for 1.784)
2021-04-07 01:01:56.937 INFO 11940 --- [ main] c.c.d.l.PrepareProgressApplicationRunner : [PPAR]-[run]-[INF]-[IC0001]:[加载所有的缓存数据到内存中.]
2021-04-07 01:01:56.937 INFO 11940 --- [ main] c.c.d.l.PrepareProgressCommandRunner : [PPCR]-[run]-[INF]-[IC0001]:[加载所有的缓存数据到内存中.]
2021-04-07 01:01:56.937 INFO 11940 --- [ main] c.c.d.listener.PrepareProcessListener : [PPL]-[onApplicationEvent]-[INF]-[IC0001]:[加载所有的缓存数据到内存中.]