一张图帮你记忆,Spring Boot 应用在启动阶段执行代码的几种方式java
原创 tan日拱一兵 日拱一兵 2019-12-30spring
前言编程
有时候咱们须要在应用启动时执行一些代码片断,这些片断多是仅仅是为了记录 log,也多是在启动时检查与安装证书 ,诸如上述业务要求咱们可能会常常碰到设计模式
Spring Boot 提供了至少 5 种方式用于在应用启动时执行代码。咱们应该如何选择?本文将会逐步解释与分析这几种不一样方式数组
CommandLineRunner安全
CommandLineRunner 是一个接口,经过实现它,咱们能够在 Spring 应用成功启动以后 执行一些代码片断springboot
@Slf4j @Component @Order(2) public class MyCommandLineRunner implements CommandLineRunner { @Override public void run(String... args) throws Exception { log.info("MyCommandLineRunner order is 2"); if (args.length > 0){ for (int i = 0; i < args.length; i++) { log.info("MyCommandLineRunner current parameter is: {}", args[i]); } } } }
当 Spring Boot 在应用上下文中找到 CommandLineRunner bean,它将会在应用成功启动以后调用 run() 方法,并传递用于启动应用程序的命令行参数app
经过以下 maven 命令生成 jar 包:maven
mvn clean package
经过终端命令启动应用,并传递参数:ide
java -jar springboot-application-startup-0.0.1-SNAPSHOT.jar --foo=bar --name=rgyb
查看运行结果:
到这里咱们能够看出几个问题:
不要使用 @Order 太多
看到 order 这个 "黑科技" 咱们会以为它能够很是方便将启动逻辑按照指定顺序执行,但若是你这么写,说明多个代码片断是有相互依赖关系的,为了让咱们的代码更好维护,咱们应该减小这种依赖使用
小结
若是咱们只是想简单的获取以空格分隔的命令行参数,那 MyCommandLineRunner 就足够使用了
ApplicationRunner
上面提到,经过命令行启动并传递参数,MyCommandLineRunner 不能解析参数,若是要解析参数,那咱们就要用到 ApplicationRunner 参数了
@Component @Slf4j @Order(1) public class MyApplicationRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { log.info("MyApplicationRunner order is 1"); log.info("MyApplicationRunner Current parameter is {}:", args.getOptionValues("foo")); } }
从新打 jar 包,运行以下命令:
java -jar springboot-application-startup-0.0.1-SNAPSHOT.jar --foo=bar,rgyb
运行结果以下:
到这里咱们能够看出:
ApplicationListener
若是咱们不须要获取命令行参数时,咱们能够将启动逻辑绑定到 Spring 的 ApplicationReadyEvent 上
@Slf4j @Component @Order(0) public class MyApplicationListener implements ApplicationListener<ApplicationReadyEvent> { @Override public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { log.info("MyApplicationListener is started up"); } }
运行程序查看结果:
到这咱们能够看出:
若是你看过我以前写的 Spring Bean 生命周期三部曲:
@PostConstruct
建立启动逻辑的另外一种简单解决方案是提供一种在 bean 建立期间由 Spring 调用的初始化方法。咱们要作的就只是将 @PostConstruct 注解添加到方法中:
@Component @Slf4j @DependsOn("myApplicationListener") public class MyPostConstructBean { @PostConstruct public void testPostConstruct(){ log.info("MyPostConstructBean"); } }
查看运行结果:
从上面运行结果能够看出:
InitializingBean
与 @PostConstruct 解决方案很是类似,咱们能够实现 InitializingBean 接口,并让 Spring 调用某个初始化方法:
@Component @Slf4j public class MyInitializingBean implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { log.info("MyInitializingBean.afterPropertiesSet()"); }
}
查看运行结果:
从上面的运行结果中,咱们获得了和 @PostConstruct 同样的效果,但两者仍是有差异的
@PostConstruct 和 afterPropertiesSet 区别
afterPropertiesSet,顾名思义「在属性设置以后」,调用该方法时,该 bean 的全部属性已经被 Spring 填充。若是咱们在某些属性上使用 @Autowired(常规操做应该使用构造函数注入),那么 Spring 将在调用afterPropertiesSet 以前将 bean 注入这些属性。但 @PostConstruct 并无这些属性填充限制
因此 InitializingBean.afterPropertiesSet 解决方案比使用 @PostConstruct 更安全,由于若是咱们依赖还没有自动注入的 @Autowired 字段,则 @PostConstruct 方法可能会遇到 NullPointerExceptions
小结
若是咱们使用构造函数注入,则这两种解决方案都是等效的
源码分析
请打开你的 IDE (重点代码已标记注释):
MyCommandLineRunner 和 ApplicationRunner 是在什么时候被调用的呢?
打开 SpringApplication.java 类,里面有 callRunners 方法
private void callRunners(ApplicationContext context, ApplicationArguments args) { List<Object> runners = new ArrayList<>(); //从上下文获取 ApplicationRunner 类型的 bean runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); //从上下文获取 CommandLineRunner 类型的 bean runners.addAll(context.getBeansOfType(CommandLineRunner.class).values()); //对两者进行排序,这也就是为何两者的 order 是能够共享的了 AnnotationAwareOrderComparator.sort(runners); //遍历对其进行调用 for (Object runner : new LinkedHashSet<>(runners)) { if (runner instanceof ApplicationRunner) { callRunner((ApplicationRunner) runner, args); } if (runner instanceof CommandLineRunner) { callRunner((CommandLineRunner) runner, args); } } }
强烈建议完整看一下 SpringApplication.java 的所有代码,Spring Boot 启动过程及原理均可以从这个类中找到一些答案
总结
最后画一张图用来总结这几种方式(阅读原文查看高清大图)
灵魂追问
上面程序运行结果, afterPropertiesSet 方法调用先于 @PostConstruct 方法,但这和咱们在 Spring Bean 生命周期之缘起 中的调用顺序偏偏相反,你知道为何吗?
MyPostConstructBean 经过 @DependsOn("myApplicationListener") 依赖了 MyApplicationListener,为何调用结果前者先与后者呢?
为何不建议 @Autowired 形式依赖注入
在写 Spring Bean 生命周期时就有朋友问我与之相关的问题,显然他们在概念上有一些含混,因此,仔细理解上面的问题将会帮助你加深对 Spring Bean 生命周期的理解
tan日拱一兵转发在看也很赞喜欢做者