我们在开发过程中会有这样的场景:需要在项目启动后执行一些操作,比如:读取配置文件信息,数据库连接,删除临时文件,清除缓存信息,工厂类初始化等。我们会有多种的实现方式,例如@PostConstruct 、CommandLineRunner、ApplicationRunner、ApplicationListener都可以实现在springboot启动后执行我们特定的逻辑,接下对比下他们的区别
该注解被用来修饰一个非静态的void方法,被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次
触发时机:
SpringBoot会把标记了Bean相关注解(例如@Component、@Service、@Repository等)的类或接口自动初始化全局的单一实例,如果标记了初始化顺序会按照用户标记的顺序,否则按照默认顺序初始化。在初始化的过程中,执行完一个Bean的构造方法后会执行该Bean的@PostConstruct方法(如果有),然后初始化下一个Bean。
spring中bean的创建过程
配置Bean(@Component、@Service、@Controller等注解配置) -----> 解析为Bean的元数据(Bean容器中的BeanDefinition对象) --> 根据Bean的元数据生成Bean(创建bean)
创建bean的时候执行顺序
Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)
示例:
@PostConstruct
public void dispatcher() throws Exception {
// 逻辑代码
}
优点:
缺点:
使用起来很简单,只需要实现CommandLineRunner或者ApplicationRunner接口,重写run方法就行
触发时机
通过springboot启动源码:
启动后会执行 callRunners方法
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
//设置线程启动计时器
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection exceptionReporters = new ArrayList<>();
//配置系统属性:默认缺失外部显示屏等允许启动
configureHeadlessProperty();
//获取并启动事件监听器,如果项目中没有其他监听器,则默认只有EventPublishingRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
//将事件广播给listeners
listeners.starting();
try {
//对于实现ApplicationRunner接口,用户设置ApplicationArguments参数进行封装
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//配置运行环境:例如激活应用***.yml配置文件
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
//加载配置的banner(gif,txt...),即控制台图样
Banner printedBanner = printBanner(environment);
//创建上下文对象,并实例化
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//配置SPring容器
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//刷新Spring上下文,创建bean过程中
refreshContext(context);
//空方法,子类实现
afterRefresh(context, applicationArguments);
//停止计时器:计算线程启动共用时间
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
//停止事件监听器
listeners.started(context);
//开始加载资源
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, listeners, exceptionReporters, ex);
throw new IllegalStateException(ex);
}
listeners.running(context);
return context;
}
callRunners方法
private void callRunners(ApplicationContext context, ApplicationArguments args) {
//将实现ApplicationRunner和CommandLineRunner接口的类,存储到集合中
List
从上面源码可以看到 ,在springboot完全初始化完毕后,会执行CommandLineRunner和ApplicationRunner,两者唯一的区别是参数不同,但是不会影响,都可以获取到执行参数。
示例
/**
* @author
* @date 2021-08-23 16:19
*/
@Component
public class ServerDispatcher implements CommandLineRunner {
@Override
public void run(String... args){
// 逻辑代码
}
}
/**
* @author
* @date 2021-08-23 16:19
*/
@Component
public class ServerDispatcher implements ApplicationRunner {
@Override
public void run(ApplicationArguments args){
// 逻辑代码
}
}
通过事件监听我们也可以实现springboot启动执行方法。实现ApplicationListener,重写onApplicationEvent方法,便可在所有的bean加载完毕后执行
触发时机:
在IOC的容器的启动过程,当所有的bean都已经处理完成之后,spring ioc容器会有一个发布ContextRefreshedEvent事件的动作。
示例
/**
* @author
* @date 2021-08-23 16:19
*/
@Component
public class ServerDispatcher implements ApplicationListener {
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
// 逻辑代码
}
}
注意:
系统会存在两个容器,一个是root application context ,另一个就是我们自己的 projectName-servlet
context(作为root application context的子容器)
这种情况下,就会造成onApplicationEvent方法被执行两次。为了避免上面提到的问题,我们可以只在root application
context初始化完成后调用逻辑代码,其他的容器的初始化完成,则不做任何处理
@Component
@Slf4j
public class LicenseCheckListener implements ApplicationListener {
/**
* 证书subject
*/
@Value("${license.subject}")
private String subject;
/**
* 公钥别称
*/
@Value("${license.publicAlias}")
private String publicAlias;
/**
* 访问公钥库的密码
*/
@Value("${license.storePass}")
private String storePass;
/**
* 证书生成路径
*/
@Value("${license.licensePath}")
private String licensePath;
/**
* 密钥库存储路径
*/
@Value("${license.publicKeysStorePath}")
private String publicKeysStorePath;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
//root application context 没有parent
ApplicationContext context = event.getApplicationContext().getParent().getParent();
if (context == null) {
if (StringUtils.isNotBlank(licensePath)) {
log.info("++++++++ 开始安装证书 ++++++++");
LicenseVerifyParam param = new LicenseVerifyParam();
param.setSubject(subject);
param.setPublicAlias(publicAlias);
param.setStorePass(storePass);
param.setLicensePath(licensePath);
param.setPublicKeysStorePath(publicKeysStorePath);
LicenseVerify licenseVerify = new LicenseVerify();
//安装证书
licenseVerify.install(param);
log.info("++++++++ 证书安装结束 ++++++++");
}
}
}
}
总结