Springboot常用扩展点分享
以下内容基于版本: SpringBoot 2.1.3.RELEASE
背景
我们在使用Springboot进行日常开发时,经常会遇到一些需求场景,比如需要在应用启动时做一些初始化工作,或在应用退出时执行一些清理工作,这都需要对springboot的扩展点有一定了解,下面给大家介绍一下常用的一些扩展点,有备无患。
扩展点介绍
一、应用启动扩展点
启动扩展点可以使得我们在应用启动过程中或者启动完成后进行一些自定义的逻辑,比如启动时将缓存载入本地内存,或者只是简单的打印一个启动日志。
相关的扩展点有 CommandLineRunner、ApplicationRunner、SpringApplicationRunListener 、ApplicationListener等等
CommandLineRunner
个人最常用扩展点是 CommandLineRunner,它是在应用启动的最后一个阶段被触发的,使用方式非常简单,只需要创建一个自定义的类,实现CommandLineRunner接口即可。
@Component public class MyCommandLineRunner implements CommandLineRunner { @Override public void run(String... args) throws Exception { Log.log("args: {}", args); } }
注意点
- CommandLineRunner执行如果出现异常,会终止应用启动,启动失败,所以请做好异常处理。
- 可以声明多个CommandLineRunner,并且可以通过在类上添加@Order注解来设置他们的优先级
- CommandLineRunner执行时,Spring容器已经初始化完成,所以可以在CommandLineRunner中注入其他配置bean或业务bean
ApplicationRunner
ApplicationRunner与CommandLineRunner功能与使用方式相同,唯一不同的点是接口入参不同,仅此而已,使用方式如下:
@Component public class MyApplicationRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { Log.log("args: {}", args); } }
SpringApplicationRunListener
SpringApplicationRunListener属于是比较针对性的扩展点,它将应用启动过程中所有重要的节点都提供了统一的接口,只需要实现此接口即可在应用启动的多个节点进行逻辑处理,比如我们可以在 started 方法中添加一些逻辑,应用启动完成后,这个方法将被调用。
@Component public class MySpringApplicationRunListener implements SpringApplicationRunListener { public MySpringApplicationRunListener(SpringApplication application, String... args) { Log.log("args: {}, {}", application, args); } @Override public void starting() { Log.log(); } @Override public void environmentPrepared(ConfigurableEnvironment environment) { Log.log(); } @Override public void contextPrepared(ConfigurableApplicationContext context) { Log.log(); } @Override public void contextLoaded(ConfigurableApplicationContext context) { Log.log(); } @Override public void started(ConfigurableApplicationContext context) { Log.log(); } @Override public void running(ConfigurableApplicationContext context) { Log.log(); } @Override public void failed(ConfigurableApplicationContext context, Throwable exception) { Log.log(); } }
SpringApplicationRunListener接口看似美好,但是稍有不慎可能就会翻车,由于该接口中的部分方法是在Spring容器初始化完成之前就被调用的,所以在此类中注入Bean可能导致Bean加载顺序的错乱,一些意想不到的问题就随之产生。为了避免此类情况,建议不要在此类中通过注解进行bean的注入,而是通过相应方法参数中的context获取所依赖的bean进行调用( context.getBean() )
ApplicationListener
ApplicationListener是spring中比较强大的一个扩展点,它不仅可以用来监听springboot内置的事件,也可以用来帮助我们实现自定义的事件监听处理,为了实现启动完成后执行一些逻辑,我们可以监听内置事件(ApplicationReadyEvent)来实现,如下:
@Component public class MyApplicationListener implements ApplicationListener<ApplicationReadyEvent> { @Override public void onApplicationEvent(ApplicationReadyEvent event) { Log.log("event: {}", event); } }
以下是springboot全部的内置事件,这些事件贯穿了应用的整个生命周期,大家可以根据需求进行监听处理
ApplicationStartingEvent
ApplicationEnvironmentPreparedEvent
ApplicationContextInitializedEvent
ApplicationPreparedEvent
ContextRefreshedEvent
ServletWebServerInitializedEvent
ApplicationStartedEvent
ApplicationReadyEvent
ContextClosedEvent
除了以上扩展点以外,还有 SmartLifecycle、ApplicationContextAware 等接口也可以用来实现启动逻辑的需求,不过不太常用,感兴趣的同学可以了解一下。
二、应用退出扩展点
ApplicationListener
ApplicationListener又来了,在启动扩展点的场景中,我们用它监听ApplicationReadyEvent事件来实现自己的启动逻辑,现在,我们又可以通过监听 ContextClosedEvent 事件来实现我们的退出逻辑,如下:
@Component public class MyApplicationListener implements ApplicationListener<ContextClosedEvent> { public MyApplicationListener() { Log.log(); } @Override public void onApplicationEvent(ContextClosedEvent event) { Log.log("event: {}", event); } }
它会在spring容器退出前执行,阻塞主线程,直到监听逻辑执行完毕,才会开始销毁spring容器。
SmartLifecycle
应用退出扩展点没有启动扩展点这么多,那就再介绍一下通过SmartLifecycle实现应用退出扩展吧,我们可以通过实现 SmartLifecycle 的 stop 接口来实现这个需求,如下:
@Component public class MySmartLifecycle implements SmartLifecycle { private boolean running = false; @Override public void start() { Log.log(); this.running = true; } @Override public void stop() { Log.log(); } @Override public boolean isRunning() { return this.running; } }
三、Bean创建&销毁扩展点
Bean本身可以通过实现不同的接口来实现不同的扩展需求,除此之外,也可以通过创建额外的Bean(实现BeanPostProcessor接口)来处理其他所有bean的创建与销毁扩展。
Bean本身
Bean本身可以通过实现 InitializingBean、DisposableBean接口来完成相应的扩展操作,也可以使用注解(个人习惯通过实现接口的方式)
@Component public class MyBean implements InitializingBean, DisposableBean { @PostConstruct public void postConstruct() { // Bean准备开始初始化了,此时注入还未完成 Log.log(" by annotation: @PostConstruct"); } @PreDestroy public void preDestroy() { // Bean 要销毁了,做点什么吧。。。 Log.log(" by annotation: @PreDestroy"); } @Override public void destroy() throws Exception { // Bean 要销毁了,做点什么吧。。 Log.log(); } @Override public void afterPropertiesSet() throws Exception { // Bean初始化完成,注入完成 Log.log(); } }
BeanPostProcessor
当一个Bean实现类BeanPostProcessor接口后,它就能捕获所有在它后面被初始化的Bean的事件,并进行扩展,如下:
@Component public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { // 一个 bean 即将初始化,做点什么吧。。 Log.log("args: {}", beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { // 一个 bean 初始化完成咯,做点什么吧。。。 Log.log("args: {}", beanName); return bean; } }
注意
- BeanPostProcessor也是一个Bean,它只能在自己初始化完成后,才能开始捕获其他bean的初始化事件,如果一些bean此时已经初始化完成,那么这些bean的事件将无法被捕获。
- 一般情况下,实现BeanPostProcessor是优先被初始化的,但是如果尝试在BeanPostProcessor中使用或注入其他的Bean,那就会使得Bean的加载顺序被打乱,导致BeanPostProcessor失效,如果注入的bean还依赖了其他一连串的bean,这个问题就会被无限放大。
四、配置扩展点
Configuration
除了通过添加@Component注解来声明bean以外,我们还可以通过Configuration配置来来声明一些自定义的Bean,这种扩展,无需实现任何接口,相对于直接在类上声明@Component的来说,使用配置类有几个好处:
- 方便管理,我们可以将一些基础的Bean统一在配置类中进行声明,可以更好地进行管理
- 高度定制,由于配置类中是以方法返回值的方式来输出Bean,所以我们可以更好的定制一个Bean
- 灵活装配,对于@Component声明的Bean,必须在@ComponentScan扫描路径下才会生效,而Configuration可以有更多的生效方式,比如在spring.factories中声明
- 统一加载,我们可以对Configuration类添加@Condition等注解来使声明的Bean一起生效或都不生效
- 业务解耦,基于上述特点,Configuration配置类不仅可以在业务应用中灵活使用,更是基础组件高度依赖的能力,基础组件中所有的Bean必须基于配置类进行声明。
- 如下:
@Configuration public class MyAutoConfiguration { @Bean public MySimpleBean myBean() { MySimpleBean mySimpleBean = new MySimpleBean(); mySimpleBean.setHello("world"); return mySimpleBean; } }
让配置类生效有几种方式:
- (不推荐) 在类上添加@Configuration注解,并将它放在 @ComponentScan 注解配置的扫描包或子包下 (SpringBoot默认扫描包为主类所在包)
- 通过其他有效配置类通过 @Import 或 @ImportAutoConfiguration 注解进行导入 (@Import、@ImportAutoConfiguration 到底有什么区别? )
- 将它配置到 META-INF/spring.factories 文件中(一般在 src/main/resources/ 工作目录下 )
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration = com.idanchuang.demo.extension.MyAutoConfiguration
五、其他扩展点
ApplicationContextInitializer
它是Springboot启动阶段比较早的一个扩展点,它在初始化ConfigurableApplicationContext前被调用,这个时候Spring上下文还未开始刷新,Bean什么的都还未初始化,所以不能也不应该在这里依赖其他的Bean对象。
此扩展点可以用来设置一些环境变量等启动前处理。
- 创建类,实现 ApplicationContextInitializer接口
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { public MyApplicationContextInitializer() { Log.log(); } @Override public void initialize(ConfigurableApplicationContext applicationContext) { Log.log("args: {}", applicationContext); } }
- 将它配置到 META-INF/spring.factories 文件中(一般在 src/main/resources/ 工作目录下 )
# Initializers
org.springframework.context.ApplicationContextInitializer = com.idanchuang.demo.extension.MyApplicationContextInitializer
自定义ApplicationEvent
我们知道ApplicationListener除了能监听Springboot内置事件,它还能用来监听我们发布的自定义事件,我们可以通过如下方式发布自定义事件:
- 创建自定义事件类,继承ApplicationEvent
public class MyApplicationEvent extends ApplicationEvent { public MyApplicationEvent(Object source) { super(source); } }
- 实现ApplicationEventPublisherAware,获取到ApplicationEventPublisher对象,并提供发送自定义事件的方法
@Component public class MyApplicationEventPublisher implements ApplicationEventPublisherAware { private ApplicationEventPublisher publisher; @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.publisher = applicationEventPublisher; } public void hello() { this.publisher.publishEvent(new MyApplicationEvent("hello world")); } }
生命周期扩展点顺序参考
ApplicationListener#onApplicationEvent(ApplicationStartingEvent) SpringApplicationRunListener#starting EnvironmentPostProcessor#postProcessEnvironment ApplicationListener#onApplicationEvent(ApplicationEnvironmentPreparedEvent) SpringApplicationRunListener#environmentPrepared ApplicationContextInitializer#initialize ApplicationListener#onApplicationEvent(ApplicationContextInitializedEvent) SpringApplicationRunListener#contextPrepared ApplicationListener#onApplicationEvent(ApplicationPreparedEvent) SpringApplicationRunListener#contextLoaded BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry BeanDefinitionRegistryPostProcessor#postProcessBeanFactory BeanFactoryPostProcessor#postProcessBeanFactory InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation InstantiationAwareBeanPostProcessor#postProcessProperties SimpleBean#setBeanName SimpleBean#setBeanClassLoader SimpleBean#setBeanFactory SimpleBean#setEnvironment SimpleBean#setEmbeddedValueResolver SimpleBean#setResourceLoader SimpleBean#setApplicationEventPublisher SimpleBean#setMessageSource SimpleBean#setApplicationContext SimpleBean#setServletContext BeanPostProcessor#postProcessBeforeInitialization SimpleBean#postConstruct > by annotation: @PostConstruct MergedBeanDefinitionPostProcessor#postProcessBeforeInitialization SimpleBean#afterPropertiesSet BeanPostProcessor#postProcessAfterInitialization MergedBeanDefinitionPostProcessor#postProcessAfterInitialization SimpleBean#afterSingletonsInstantiated SimpleBean#isRunning SimpleBean#start ApplicationListener#onApplicationEvent(ContextRefreshedEvent ) ApplicationListener#onApplicationEvent(ServletWebServerInitializedEvent) ApplicationListener#onApplicationEvent(ApplicationStartedEvent) SpringApplicationRunListener#started CommandLineRunner#run ApplicationRunner#run ApplicationListener#onApplicationEvent(ApplicationReadyEvent) SpringApplicationRunListener#running ApplicationListener#onApplicationEvent(ContextClosedEvent) SimpleBean#isRunning SimpleBean#stop SimpleBean#preDestroy > by annotation: @PreDestroy SimpleBean#destroy