Spring IoC 容器事件
Spring容器除了提供Bean的生命周期扩展点,还需要提供容器的生命周期扩展点。容器不像bean一样是由开发者定义的。框架代码编写的时候并不知道谁会关心。所以Spring采用的方式是将容器的生命周期通过事件机制发布出来,关心事件的开发者自行订阅。这是一个观察者模式的典型应用。
场景
看一个简单的场景。有动态配置A,当它发生变化的时候需要按配置重新组织一个责任链的顺序,如果配置错误则不生效,使用上一次的配置并且告警。
public class EhanceFilterChain implements ApplicationContextAware {
private ApplicationContext applicationContext;
public List<IFilter> filterList;
/**
* filter顺序的动态配置
**/
@DynamicValue(key="ehance_filter_chain_filters")
public String filterListConfig;
/**
* 动态配置变化监听函数
**/
@DynamicValueListerner(key="ehance_filter_chain_filters")
public void orderFilterList(){
// 动态配置变更,组织责任链。如果配置错误则告警
List<IFilter> filterListNew = new ArrayList<IFilter>();
try{
for(...){
filterListNew.add(applicationContext.getBean("filterBeanName"));
}
this.filterList = filterListNew;
}
catch(Exception ex){
/// monitor alert
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
那么问题来了,如果配置错误,还没处理前,应用被重启,处理错误导致责任链为空,容器正常启动,服务正常暴露出去。在关键业务这种情况是不允许的。启动中如果解析错误则需要让服务停止对外暴露。那么应该如何判断容器是否是启动或者bean刷新呢 ?
解决
Spring容器提供了容器的事件机制,能够监听容器的生命周期事件。只需要实现ApplicationListener接口。
public class EhanceFilterChain implements ApplicationListener<ContextRefreshedEvent> {
// 省略其他
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
ApplicationContext context = contextRefreshedEvent.getApplicationContext();
if(context.getParent() == null){
List<IFilter> filterListNew = new ArrayList<IFilter>();
try{
for(...){
filterListNew.add(applicationContext.getBean("filterBeanName"));
}
this.filterList = filterListNew;
}
catch(Exception ex){
// 关闭服务
context.close();
}
}
}
}
容器事件
Spring容器事件是一个典型的观察者模式,它提供了一种容器的扩展机制。内置的容器事件有ContextRefreshedEvent
ContextStartedEvent
ContextStoppedEvent
ContextClosedEvent
分别对应着容器生命周期的 refresh、start、stop、和close方法。同时它还提供了开发者自定义事件的方法。
首先需要定义一个事件,继承自ApplicationEvent
public class BlackListEvent extends ApplicationEvent {
private final String address;
private final String content;
public BlackListEvent(Object source, String address, String content) {
super(source);
this.address = address;
this.content = content;
}
// accessor and other methods...
}
接着定义一个被观察者,它需要有一个ApplicationEventPublisher来发布事件,容器提供了一个接口ApplicationEventPublisherAware来注入ApplicationEventPublisher。而ApplicationEventPublisher使用publishEvent方法发布事件。
public class EmailService implements ApplicationEventPublisherAware {
private List<String> blackList;
private ApplicationEventPublisher publisher;
public void setBlackList(List<String> blackList) {
this.blackList = blackList;
}
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void sendEmail(String address, String content) {
if (blackList.contains(address)) {
publisher.publishEvent(new BlackListEvent(this, address, content));
return;
}
// send email...
}
}
事件能够发布以后,就需要实现一个或者多个观察者。它们都需要实现ApplicationListener接口,在收到事件后进行对应的处理。
public class BlackListNotifier implements ApplicationListener<BlackListEvent> {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
public void onApplicationEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
其他的事情是由Spring容器代劳的,开发者只需要关心事件的发布和订阅。并且两种动作是可以解耦的。发布者并不知道订阅者的存在。
缺点
容器事件可以做到设计的解耦,但是通知范围仅仅存在于容器内部,或者说是单进程内。它只能做代码级别的解耦。在分布式环境中用处并不大。分布式环境下的事件通知还是要使用消息队列中间件。
- Elasticsearch推荐插件篇(head,sense,marvel)
- sql基础知识:日期的常用用法
- 《Effective Java》—— 对于所有对象都通用的方法
- 《Effective Java》—— 创建与销毁对象
- web调试工具——Fiddler使用介绍(一)
- Windows c++应用程序通用日志组件(组件及测试程序下载)
- 快速排序
- 如何在Elasticsearch中安装中文分词器(IK+pinyin)
- Python抓取中文网页
- 《Effective Java》—— 读后总结
- unix共享内存要点
- LAMP=Linux+Apache+Mysql+Php
- unix共享内存要点
- Elasticsearch —— bulk批量导入数据
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- 听说Memcache你很豪横?-------------深入剖析Memcache+keepalive高可用群集
- 排障集锦:九九八十一难之第十四难!------------- 安装magent时make编译报错
- 【Rust日报】2020-09-06 Evil_DLL 用来测试注入方法的DLL
- 【Rust日报】2020-09-05 微软在c++静态分析工具实现了一些rust的安全规则
- 一起来玩玩WebGL
- 自研网关:多项目的swagger聚合功能
- Prometheus 如何做到“活学活用”,大牛总结的避坑指南
- 文档驱动 —— 表单组件(一):表单元素组件 优点缺点选择文本类的Inputcheck 多选value的类型问题
- Vue3.0源码结构分析
- 【每周一库】- cached - 缓存结构型、辅助函数记忆化
- meta生成器 —— 表单元素组件 meta表单代码meta的模板data变幻
- 不用写代码也能做表单 —— 加载meta即可 菜单表单加载json运行效果。ModelAbout
- 从0到1,手把手教你入门 etcd
- 数据结构:手把手带你了解 ”图“ 所有知识!(含DFS、BFS)
- 设计模式之单例模式