【CSDN 编者按】本文总结了Spring Boot容器初始化和Java Bean初始化两个维度总计七种可以由开发者自定义的Spring Boot扩展点,开发者可以根据这些扩展点实现消息监听,配置监听,甚至自定义事件,。-订阅的观察者模式。

作者 | L       责编 | 欧阳姝黎

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

容器刷新完成扩展点

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

基本用法

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

@Component
public class StartupApplicationListenerExample implements ApplicationListener<ContextRefreshedEvent> {private static final Logger LOG = Logger.getLogger(StartupApplicationListenerExample.class);public static int counter;@Override public void onApplicationEvent(ContextRefreshedEvent event) {LOG.info("Increment counter");counter++;}
}

易错的点

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

@Component
public class StartupApplicationListenerExample implements ApplicationListener<ContextRefreshedEvent> {private static final Logger LOG = Logger.getLogger(StartupApplicationListenerExample.class);public static int counter;@Override public void onApplicationEvent(ContextRefreshedEvent event) {if (event.getApplicationContext().getParent() == null) {// root application context 没有parentLOG.info("Increment counter");counter++;}}
}

高阶玩法

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

  • 先自定义一个事件:

public class NotifyEvent extends ApplicationEvent {private String email;private String content;public NotifyEvent(Object source) {super(source);}public NotifyEvent(Object source, String email, String content) {super(source);this.email = email;this.content = content;}// 省略getter/setter方法
}
  • 注册一个事件监听器

@Component
public class NotifyListener implements ApplicationListener<NotifyEvent> {@Overridepublic void onApplicationEvent(NotifyEvent event) {System.out.println("邮件地址:" + event.getEmail());System.out.println("邮件内容:" + event.getContent());}
}
  • 发布事件

@RunWith(SpringRunner.class)
@SpringBootTest
public class ListenerTest {@Autowiredprivate WebApplicationContext webApplicationContext;@Testpublic void testListener() {NotifyEvent event = new NotifyEvent("object", "abc@qq.com", "This is the content");webApplicationContext.publishEvent(event);}
}
  • 执行单元测试可以看到邮件的地址和内容都被打印出来了

1.2 SpringBoot 的 CommandLineRunner 接口

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

@Component
public class CommandLineAppStartupRunner implements CommandLineRunner {private static final Logger LOG =LoggerFactory.getLogger(CommandLineAppStartupRunner.class);public static int counter;@Overridepublic void run(String...args) throws Exception {LOG.info("Increment counter");counter++;}
}

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

  • 多个实现了 CommandLineRunner 的 Bean 的执行顺序可以根据 Bean 上的 @Order 注解调整

  • 其run方法可以接受从控制台输入的参数,跟ApplicationListener<ContextRefreshedEvent>这种扩展相比,更加灵活

// 从控制台输入参数示例
java -jar CommandLineAppStartupRunner.jar abc abcd

1.3 SpringBoot 的 ApplicationRunner 接口

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

@Component
public class AppStartupRunner implements ApplicationRunner {private static final Logger LOG =LoggerFactory.getLogger(AppStartupRunner.class);public static int counter;@Overridepublic void run(ApplicationArguments args) throws Exception {LOG.info("Application started with option names : {}", args.getOptionNames());LOG.info("Increment counter");counter++;}
}

比如:

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

Bean 初始化完成扩展点

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

2.1 @PostConstruct 注解

@PostConstruct 注解一般放在 Bean 的方法上,被 @PostConstruct 修饰的方法会在 Bean 初始化后马上调用:

@Component
public class PostConstructExampleBean {private static final Logger LOG = Logger.getLogger(PostConstructExampleBean.class);@Autowiredprivate Environment environment;@PostConstructpublic void init() {LOG.info(Arrays.asList(environment.getDefaultProfiles()));}
}

2.2 InitializingBean 接口

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

@Component
public class InitializingBeanExampleBean implements InitializingBean {private static final Logger LOG = Logger.getLogger(InitializingBeanExampleBean.class);@Autowiredprivate Environment environment;@Overridepublic void afterPropertiesSet() throws Exception {LOG.info(Arrays.asList(environment.getDefaultProfiles()));}
}

2.3 @Bean 注解的初始化方法

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

Bean 的定义

public class InitMethodExampleBean {private static final Logger LOG = Logger.getLogger(InitMethodExampleBean.class);@Autowiredprivate Environment environment;public void init() {LOG.info(Arrays.asList(environment.getDefaultProfiles()));}
}

Bean注入

@Bean(initMethod="init")
public InitMethodExampleBean initMethodExampleBean() {return new InitMethodExampleBean();
}

2.4 通过构造函数注入

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

@Component
public class LogicInConstructorExampleBean {private static final Logger LOG = Logger.getLogger(LogicInConstructorExampleBean.class);private final Environment environment;@Autowiredpublic LogicInConstructorExampleBean(Environment environment) {this.environment = environment;LOG.info(Arrays.asList(environment.getDefaultProfiles()));}
}

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

可以用一个简单的测试:

@Component
@Scope(value = "prototype")
public class AllStrategiesExampleBean implements InitializingBean {private static final Logger LOG = Logger.getLogger(AllStrategiesExampleBean.class);public AllStrategiesExampleBean() {LOG.info("Constructor");}@Overridepublic void afterPropertiesSet() throws Exception {LOG.info("InitializingBean");}@PostConstructpublic void postConstruct() {LOG.info("PostConstruct");}public void init() {LOG.info("init-method");}
}

实例化这个Bean后输出:

[main] INFO o.b.startup.AllStrategiesExampleBean - Constructor
[main] INFO o.b.startup.AllStrategiesExampleBean - PostConstruct
[main] INFO o.b.startup.AllStrategiesExampleBean - InitializingBean
[main] INFO o.b.startup.AllStrategiesExampleBean - init-method

2020-2021中国开发者调查报告重磅来袭,直接扫码或微信搜索「CSDN」公众号,后台回复关键词「开发者」,快速获取完整的报告内容!

七种方式教你在 SpringBoot 初始化时搞点事情相关推荐

  1. 技术研究院003---六种方式,教你在SpringBoot初始化时搞点事情!

    前言 在实际工作中总是需要在项目启动时做一些初始化的操作,比如初始化线程池.提前加载好加密证书....... 那么经典问题来了,这也是面试官经常会问到的一个问题:有哪些手段在Spring Boot 项 ...

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

    前言 在实际工作中总是需要在项目启动时做一些初始化的操作,比如初始化线程池.提前加载好加密证书....... 那么经典问题来了,这也是面试官经常会问到的一个问题:有哪些手段在Spring Boot 项 ...

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

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

  4. 七种方法教你如何获取以太坊测试网Token

    七种方法教你如何获取以太坊测试网Token 在使用以太坊测试网时,我们通常都需要获取一些测试币,这里就以MetaMask为例介绍一下怎么获取以太坊测试币. 首先介绍一种最权威且一直可以用的方法,以Ro ...

  5. MFC对话框控件访问的七种方式

    void CTestDlg::OnButtonAdd() {// TODO: Add your control notification handler code here//动态创建按钮 /* if ...

  6. Python合并字典的七种方式!

    Python有很多高级属性,例如合并字典就有七种方式可以实现.小千今天就来给大家展示一下,避免将来遇到的时候不知道是做什么的,提前了解一下以备不时之需. 1.最简单的原地更新 字典对象内置了一个 up ...

  7. Vue.js 定义组件模板的七种方式

    转载自  Vue.js 定义组件模板的七种方式 在 Vue 中定义一个组件模板,至少有七种不同的方式(或许还有其它我不知道的方式): 字符串 模板字面量 x-template 内联模板 render ...

  8. 我赢助手详解:抖音变现目前流行的是七种方式之直播变现和Ip变现

    抖音变现目前流行的是七种方式,电商卖货.广告营销.内容付费.品牌导流.直播变现.IP变现.社群营销. 今天我们来说一说直播变现模式.直播变现,他有两种模式: 第一种就是直接在直播中去带货,然后就转化. ...

  9. 短视频运营详解:抖音变现目前流行的是七种方式之一电商卖货

    短视频运营详解:抖音变现目前流行的是七种方式之电商卖货 抖音变现目前流行的是七种方式,电商卖货.广告营销.内容付费.品牌导流.直播变现.IP变现.社群营销. 我们分别来说一下:权威的三方报告提到过,抖 ...

最新文章

  1. asp.net FileUpload随想随记
  2. Linux下用arptables防arp攻击
  3. 分组[测试点分支+二分图判定]
  4. java head head.next_Java: 链表head取出用后,置next=null为何可以加速gc?
  5. Codeforces Round #632 (Div. 2) E. Road to 1600 构造好题
  6. 傻子坐飞机问题的求解
  7. leetcode - 46. 全排列(对vector容器的元素进行搜索,判断是否存在vector中)
  8. Unity 利用NGUI做屏幕分辨率适配+学习UIDraggablePanel的使用
  9. 【未来已来】百度老板李彦宏:人工智能是下一道主菜
  10. mysql在test库中创建表stu_1.在mysql的test数据库中新建表,表名为student,表结构如下:...
  11. 机器视觉算法与应用总结
  12. 逻辑推理判断 —— 每周一测(智者参与)
  13. matlab篮球队需要五名队员,球队战绩影响因素分析.doc
  14. Improved Robustness to Open Set Inputs viaTempered Mixup
  15. 信用卡评分模型(R语言)
  16. 劈开迷雾:蘑菇街搜索架构及搜索排序实践
  17. 微信支付密码设置html,微信支付页面怎么加密码锁屏(微信支付界面怎么上锁)...
  18. Java如何将组件添加到桌面上,做Swing桌面程序,该怎样将组件与业务逻辑分离?...
  19. 你真的了解分类模型评估指标都有哪些吗?【附Python代码实现】
  20. 利用python 批量下载美拍视频

热门文章

  1. 红皮书--EOF与BOF
  2. springboot启动报错:Error starting ApplicationContext. To display the conditions report re-run your appl
  3. 以后别写程序了,几个程序员很有用的源码网站奉献给大家
  4. 移动开发day4_京东移动页面
  5. 谈谈控制器技术SpringMVC与struts2
  6. bzoj2733 永无乡 splay树的启发式合并
  7. Mac os 下的文件权限管理
  8. java中的POJO、PO、VO分别是什么?
  9. 离开APM的弹性云还是真弹性吗
  10. 一个阿拉伯数字转中文数字的函数