简介
业务需求需要在项目启动之后自动把执行一次方法 (数据初始化或者创建一些调度任务),但是有时候可能不太明确他们的执行顺序,本文就带你梳理一下它们的执行顺序
方法 / 步骤
一: 实现 CommandLineRunner接口重写run()方法
容器启动之后,加载实现类的逻辑资源,已达到完成资源初始化的任务
/**
* Description:
*
* @Author: YangGC
* DateTime: 10-01
*/
@Component
public class InitConfigCommandLineRunner implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(InitConfigCommandLineRunner.class);
@Override
public void run(String... args) throws Exception {
logger.info("项目启动初始化开始CommandLineRunner");
}
}
二: 实现 ApplicationRunner 接口重写run()方法
ApplicationRunner 也可以实现同样的功能,该方法的参数是ApplicationArguments,解析封装过后的args参数, 通过该对象既可以拿到原始命令行参数。 且在不设置@Order
的情况下application的优先级要大于command, 该方法比@PostConstruct注解要晚。
/**
* Description:
*
* @Author: YangGC
* DateTime: 10-01
*/
@Component
public class InitConfigApplicationRunner implements ApplicationRunner {
private static final Logger logger = LoggerFactory.getLogger(InitConfigApplicationRunner.class);
@Override
public void run(ApplicationArguments args) throws Exception {
logger.info("项目启动初始化开始ApplicationRunner");
}
}
根据日志可以看出ApplicationRunner 是在started之后初始化的
三:监听ApplicationListener上下文事件
实现ApplicationListener接口,并实现 onApplicationEvent(ApplicationReadyEvent applicationReadyEvent)方法
❗ 如果有执行多次的情况可以 点击查看 解决方案
@Component
public class InitConfigApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
private static final Logger logger = LoggerFactory.getLogger(InitConfigApplicationListener.class);
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
logger.info("项目启动初始化开始ContextRefreshedEvent");
}
}
官方文档:
如果在 SpringApplication 启动后需要运行某些特定的代码,则可以实现 ApplicationRunner 或 CommandLineRunner 接口。两个接口的工作方式相同,都提供一个 run 方法,在 SpringApplication.run (...)完成之前调用这个方法。
如果定义了几个 CommandLineRunner 或 ApplicationRunner bean,记得使用特定的顺序调用它们
定义静态常量,随着类的生命周期加载而提前加载(这种方式可能对于工作经验较少的伙伴,选择是最多的)
四: 实现InitializingBean接口重写 afterPropertiesSet()方法
InitializingBean接口只包含一个方法afterPropertiesSet(),凡是继承了InitializingBean接口的类,在初始化时都会调用这方法;
使用方法分为三个步骤:1、 被spring管理 2、 实现InitializingBean接口 3、重写afterPropertiesSet方法
@Component
public class InitConfigInitializingBean implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(InitConfigInitializingBean.class);
@Override
public void afterPropertiesSet() throws Exception {
logger.info("项目启动初始化开始InitializingBean");
}
}
五:方法注解 @PostConstruct
很多人滥用该注解,并且认为该注解是基于Spring的。其实不然,@PostConstruct和@PreDestroy是在Java EE 5引入并且基于Servlet规范, 位于javax.annotation包下,Java最初的设计者认为,这些功能并不是Java核心API,因此就放到了扩展包中。该注解执行的优先级很高。
作用在于声明一个Bean对象初始化完成后执行的,方法比ApplicationRunner要快。有下面几个特点:
- 只有一个非静态方法能使用此注解
- 被注解的方法不得有任何参数
- 被注解的方法返回值必须为void
- 被注解方法不得抛出已检查异常
- 此方法只会被执行一次
❗ 该注解是用来作为Servlet生命周期控制的注解,用在这方面没问题,但是,如果再在里面做加载bean的动作就不行了。因为在执行被该注解方法的时间点上,它只能保证自己的bean被装载,但并不能保证所有被需要的bean被装载,所以在方法内加载bean会报错,导致初始化异常。
如果非要使用这么做,可以用开启子线程,使用线程自旋等待的方式获取bean,在子线程中处理数据,但是该注解的设计并不是用于干这个事的,所以不推荐。
/**
* Description:
*
* @Author: YangGC
* DateTime: 10-01
*/
@Component
public class InitConfig4PostConstruct{
private static final Logger logger = LoggerFactory.getLogger(InitConfig4PostConstruct.class);
@PostConstruct
public void init() throws Exception {
logger.info("项目启动初始化开始PostConstruct");
}
}
六:结论
- 结论 (默认不添加order关键字情况下),初始化的先后顺序如下面所示:
@PostConstruct--> InitializingBean --> ApplicationListener监听 --> ApplicationRunner --> CommandLineRunner
- 测试结果
ApplicationRunner 与CommandLineRunner工作方式相同,唯一的区别在于两种方法入参方式不同,实现这两个接口就可以让应用程序代码在启动完成后,接收流量前被调用。如果实现了多次,则必须实现org.springframework.core.Ordered或者使用org.springframework.core.annotation.Order注解来定义他们之间的顺序。
推荐使用: ApplicationRunner 或者 CommandLineRunner方式