七种方式,教你在SpringBoot初始化时搞点事情!

开发 后端
我们经常需要在容器启动的时候做一些钩子动作,比如注册消息消费者,监听配置等,今天就总结下SpringBoot留给开发者的7个启动扩展点。

[[390889]]

 我们经常需要在容器启动的时候做一些钩子动作,比如注册消息消费者,监听配置等,今天就总结下SpringBoot留给开发者的7个启动扩展点。

容器刷新完成扩展点

1、监听容器刷新完成扩展点ApplicationListener<ContextRefreshedEvent>

基本用法

熟悉Spring的同学一定知道,容器刷新成功意味着所有的Bean初始化已经完成,当容器刷新之后Spring将会调用容器内所有实现了ApplicationListener<ContextRefreshedEvent>的Bean的onApplicationEvent方法,应用程序可以以此达到监听容器初始化完成事件的目的。 

  1. @Component  
  2. public class StartupApplicationListenerExample implements   
  3.   ApplicationListener<ContextRefreshedEvent> {  
  4.     private static final Logger LOG   
  5.       = Logger.getLogger(StartupApplicationListenerExample.class);  
  6.     public static int counter;  
  7.     @Override public void onApplicationEvent(ContextRefreshedEvent event) {  
  8.         LOG.info("Increment counter");  
  9.         counter++;  
  10.     }  

易错的点

这个扩展点用在web容器中的时候需要额外注意,在web 项目中(例如spring mvc),系统会存在两个容器,一个是root application context,另一个就是我们自己的context(作为root application context的子容器)。如果按照上面这种写法,就会造成onApplicationEvent方法被执行两次。解决此问题的方法如下: 

  1. @Component  
  2. public class StartupApplicationListenerExample implements   
  3.   ApplicationListener<ContextRefreshedEvent> {  
  4.     private static final Logger LOG   
  5.       = Logger.getLogger(StartupApplicationListenerExample.class);  
  6.     public static int counter;  
  7.     @Override public void onApplicationEvent(ContextRefreshedEvent event) {  
  8.         if (event.getApplicationContext().getParent() == null) {  
  9.             // root application context 没有parent  
  10.             LOG.info("Increment counter");  
  11.             counter++;  
  12.         }  
  13.     }  

高阶玩法

当然这个扩展还可以有更高阶的玩法:自定义事件,可以借助Spring以最小成本实现一个观察者模式:

  •  先自定义一个事件: 
  1. public class NotifyEvent extends ApplicationEvent {  
  2.     private String email;  
  3.     private String content;  
  4.     public NotifyEvent(Object source) {  
  5.         super(source);  
  6.     }  
  7.     public NotifyEvent(Object source, String email, String content) { 
  8.          super(source);  
  9.         this.email = email;  
  10.         this.content = content;  
  11.     }  
  12.     // 省略getter/setter方法  
  •  注册一个事件监听器 
  1. @Component  
  2. public class NotifyListener implements ApplicationListener<NotifyEvent> {  
  3.     @Override  
  4.     public void onApplicationEvent(NotifyEvent event) {  
  5.         System.out.println("邮件地址:" + event.getEmail());  
  6.         System.out.println("邮件内容:" + event.getContent());  
  7.     }  
  •  发布事件 
  1. @RunWith(SpringRunner.class)  
  2. @SpringBootTest  
  3. public class ListenerTest {  
  4.     @Autowired  
  5.     private WebApplicationContext webApplicationContext;  
  6.     @Test  
  7.     public void testListener() {  
  8.         NotifyEvent event = new NotifyEvent("object", "abc@qq.com", "This is the content");  
  9.         webApplicationContext.publishEvent(event);  
  10.     }  
  •  执行单元测试可以看到邮件的地址和内容都被打印出来了

2、SpringBoot的CommandLineRunner接口

当容器上下文初始化完成之后,SpringBoot也会调用所有实现了CommandLineRunner接口的run方法,下面这段代码可起到和上文同样的作用: 

  1. @Component  
  2. public class CommandLineAppStartupRunner implements CommandLineRunner {  
  3.     private static final Logger LOG =  
  4.       LoggerFactory.getLogger(CommandLineAppStartupRunner.class);  
  5.     public static int counter;  
  6.     @Override  
  7.     public void run(String...args) throws Exception { 
  8.         LOG.info("Increment counter");  
  9.         counter++;  
  10.     }  

对于这个扩展点的使用有额外两点需要注意:

  • 多个实现了CommandLineRunner的Bean的执行顺序可以根据Bean上的@Order注解调整
  • 其run方法可以接受从控制台输入的参数,跟ApplicationListener<ContextRefreshedEvent>这种扩展相比,更加灵活 
  1. // 从控制台输入参数示例  
  2. java -jar CommandLineAppStartupRunner.jar abc abcd 

3、SpringBoot的ApplicationRunner接口

这个扩展和SpringBoot的CommandLineRunner接口的扩展类似,只不过接受的参数是一个ApplicationArguments类,对控制台输入的参数提供了更好的封装,以--开头的被视为带选项的参数,否则是普通的参数 

  1. @Component  
  2. public class AppStartupRunner implements ApplicationRunner {  
  3.     private static final Logger LOG =  
  4.       LoggerFactory.getLogger(AppStartupRunner.class);  
  5.     public static int counter;  
  6.     @Override  
  7.     public void run(ApplicationArguments args) throws Exception {  
  8.         LOG.info("Application started with option names : {}",   
  9.           args.getOptionNames());  
  10.         LOG.info("Increment counter");  
  11.         counter++;  
  12.     }  

比如:

  1. java -jar CommandLineAppStartupRunner.jar abc abcd --autho=mark verbose 

Bean初始化完成扩展点

前面的内容总结了针对容器初始化的扩展点,在有些场景,比如监听消息的时候,我们希望Bean初始化完成之后立刻注册监听器,而不是等到整个容器刷新完成,Spring针对这种场景同样留足了扩展点:

1、@PostConstruct注解 

  1. @PostConstruct注解一般放在Bean的方法上,被@PostConstruct修饰的方法会在Bean初始化后马上调用:  
  2. @Component  
  3. public class PostConstructExampleBean {  
  4.     private static final Logger LOG   
  5.       = Logger.getLogger(PostConstructExampleBean.class);  
  6.     @Autowired  
  7.     private Environment environment;  
  8.     @PostConstruct  
  9.     public void init() {  
  10.         LOG.info(Arrays.asList(environment.getDefaultProfiles()));  
  11.     }  

2、 InitializingBean接口

InitializingBean的用法基本上与@PostConstruct一致,只不过相应的Bean需要实现afterPropertiesSet方法 

  1. @Component  
  2. public class InitializingBeanExampleBean implements InitializingBean {  
  3.     private static final Logger LOG   
  4.       = Logger.getLogger(InitializingBeanExampleBean.class);  
  5.     @Autowired  
  6.     private Environment environment;  
  7.     @Override 
  8.     public void afterPropertiesSet() throws Exception {  
  9.         LOG.info(Arrays.asList(environment.getDefaultProfiles()));  
  10.     }  

3、@Bean注解的初始化方法

通过@Bean注入Bean的时候可以指定初始化方法:

Bean的定义 

  1. public class InitMethodExampleBean {  
  2.     private static final Logger LOG = Logger.getLogger(InitMethodExampleBean.class);  
  3.     @Autowired  
  4.     private Environment environment;  
  5.     public void init() {  
  6.         LOG.info(Arrays.asList(environment.getDefaultProfiles()));  
  7.     }  

Bean注入 

  1. @Bean(initMethod="init" 
  2. public InitMethodExampleBean initMethodExampleBean() {  
  3.     return new InitMethodExampleBean();  

4、通过构造函数注入

Spring也支持通过构造函数注入,我们可以把搞事情的代码写在构造函数中,同样能达到目的 

  1. @Component   
  2. public class LogicInConstructorExampleBean {  
  3.     private static final Logger LOG   
  4.       = Logger.getLogger(LogicInConstructorExampleBean.class);  
  5.     private final Environment environment;  
  6.     @Autowired  
  7.     public LogicInConstructorExampleBean(Environment environment) {  
  8.         this.environment = environment;  
  9.         LOG.info(Arrays.asList(environment.getDefaultProfiles()));  
  10.     }  

Bean初始化完成扩展点执行顺序?

可以用一个简单的测试: 

  1. @Component  
  2. @Scope(value = "prototype" 
  3. public class AllStrategiesExampleBean implements InitializingBean {  
  4.     private static final Logger LOG   
  5.       = Logger.getLogger(AllStrategiesExampleBean.class);  
  6.     public AllStrategiesExampleBean() {  
  7.         LOG.info("Constructor");  
  8.     }  
  9.     @Override  
  10.     public void afterPropertiesSet() throws Exception {  
  11.         LOG.info("InitializingBean");  
  12.     }  
  13.     @PostConstruct  
  14.     public void postConstruct() { 
  15.         LOG.info("PostConstruct");  
  16.     }  
  17.     public void init() {  
  18.         LOG.info("init-method");  
  19.     }  

实例化这个Bean后输出: 

  1. [main] INFO o.b.startup.AllStrategiesExampleBean - Constructor  
  2. [main] INFO o.b.startup.AllStrategiesExampleBean - PostConstruct  
  3. [main] INFO o.b.startup.AllStrategiesExampleBean - InitializingBean  
  4. [main] INFO o.b.startup.AllStrategiesExampleBean - init-method  

 

责任编辑:庞桂玉 来源: Java知音
相关推荐

2018-06-10 16:31:12

2017-06-14 16:44:15

JavaScript原型模式对象

2010-09-17 17:51:04

2022-07-01 08:00:44

异步编程FutureTask

2022-03-18 14:33:22

限流算法微服务

2022-12-23 10:55:09

CIO方式团队

2021-07-23 17:15:12

物联网IOT

2020-01-16 12:20:03

人工智能AI税收

2023-01-03 13:43:55

团队首席信息官

2023-09-07 10:39:25

AI供应链

2023-07-06 10:36:51

人工智能

2023-09-11 14:26:44

智能技术人工智能

2010-10-15 10:02:01

Mysql表类型

2017-06-30 08:51:12

组件模板勒索软件项目管理

2022-06-09 18:09:59

农业物联网IOT

2023-10-19 17:30:50

2019-10-29 06:30:31

告警疲劳网络安全安全风险

2022-05-10 08:08:01

find命令Linux

2020-01-14 08:00:00

.NET缓存编程语言

2023-12-22 14:27:30

点赞
收藏

51CTO技术栈公众号