观察者设计模式
- 概念
- 角色
- spring对观察者设计模式的实现
- 上课事件
- 两个发布者
- 监听器
- 发布事件
- 原理
- 应用
- 生产应用
- 观察者设计模式在配置中心中的应用
- 原理(广播器)
- 多线程广播器
- 自定义广播器
概念
它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
在观察者模式中,主体是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅并接收通知。
角色
事件,被观察者(事件发布者、事件源),观察者。
举个例子:
同学们正在上自习课,体育老师过来说“走,上课去!”,同学们跑去了操场。
第二天自习课上计算机老师过来说“走,上课去!”,同学们跑去了机房。
这个例子中两位老师是是事件的发布者(被观察者),“走,上课去!”这是一个事件。
而同学们呢就是观察者,同学们收到了相同的事件还需要知道事件源是谁(事件是谁发布的),这样才能正确的处理事件。
spring对观察者设计模式的实现
-
ApplicationEvent
:事件,上课事件需要继承这个类 -
ApplicationListener
:观察者(监听者,监听器),学生需要实现这个接口 -
ApplicationEventPublisher
:事件发布器,这是一个工具类,谁使用这个工具谁就是“被观察者(事件源、发布者)”,教师会使用这个工具类
上课事件
/**
* 上课事件,继承spring提供的事件
*/
public class ClassBeginsEvent extends ApplicationEvent {
/**
* 设置事件源
* 相同的事件由不同的事件源(被观察者)发布,观察者执行的动作可能不一样
* 计算机老师说上课和体育老师说上课,学生的反应肯定不一样
*
* @param source 事件源(被观察者)
*/
public ClassBeginsEvent(Object source) {
super(source);
}
}
两个发布者
/**
* 计算机老师,持有事件发布器工具类
*/
@Component
public class ComputerTeacher implements Teacher {
// 事件发布器
@Autowired
ApplicationEventPublisher applicationEventPublisher;
/**
* 这个方法相当与“走,上课去!”😄
*/
@Override
public void classBegins() {
// 发布一个上课的事件,把自己传进去(事件源)
applicationEventPublisher.publishEvent(new ClassBeginsEvent(this));
}
}
// 体育老师
@Component
public class GymEducationTeacher implements Teacher {
@Autowired
ApplicationEventPublisher applicationEventPublisher;
@Override
public void classBegins() {
applicationEventPublisher.publishEvent(new ClassBeginsEvent(this));
}
}
监听器
每一个学生都是一个监听器,都实现了ApplicationListener,虽然学生们都在上体育课但是也会做不同的运动。
@Slf4j
@Component
public class Students implements ApplicationListener<ClassBeginsEvent> {
@Override
public void onApplicationEvent(ClassBeginsEvent event) {
// 基于不同的事件源做出不同的反应
Object source = event.getSource();
if (source instanceof ComputerTeacher) {
log.info("跑去机房...");
}
if (source instanceof GymEducationTeacher) {
log.info("跑去操场...");
}
}
}
发布事件
从spring容器取出所有的Teacher(体育老师、计算机老师)调用classBegins方法(喊一声“走,上课去!”)
applicationContext.getBeansOfType(Teacher.class).values().forEach(Teacher::classBegins);
原理
被观察者持有观察者的引用,被观察者拥有所有观察者的引用。
当被观察者发布时会取出所有观察者迭代遍历观察者并执行观察者的onApplicationEvent
方法。(可以在一个新的线程中调用这个方法,主要目的也是解耦合)
如何找到所有的观察者?
spring环境中所有的观察者都实现了ApplicationListener
接口,而且所有的观察者都加了@Component
注解, 所以······
// 方式一
applicationContext.getBeansOfType(ApplicationListener.class);
// 方式二
@Autowired
List<ApplicationListener> applicationListener
这两种方式都可以,实际上是第二种
应用
生产应用
当用户的某项业务办理成功后,可以发布一个事件去处理一个或多个支线业务(多个监听器)如:发送短信对工作人员的服务进行评价、发送一个抽奖短信、生成积分等等
观察者设计模式在配置中心中的应用
ApplicationEvent
有很多实现类:
-
ContextRefreshedEvent
:容器refresh之后会spring框架会发布这个事件 -
ApplicationStartedEvent
:spring boot成功启动之后会发布这个事件
在配置较多的项目中通常会使用到配置中心如:zookeeper,Nacos,Apollo,等等。
当我们在配置中心更改了某个配置后,应用程序就会收到通知更新这个配置属性,这是怎么是实现的?
当我们使用zookeeper时通常会监听某个节点的变化,当节点发生变化后zookeeper服务会通知我们的应用程序执行监听方法。
监听方法会做什么?发布一个事件,这个事件就是RefreshEvent
。
spring cloud中有一个RefreshEventListener
监听了这个事件,大致的处理逻辑就是重新创建一个applicationContext替换原来的applicationContext
原理(广播器)
多线程广播器
从上面的例子我们知道教师会使用ApplicationEventPublisher applicationEventPublisher;
这个工具发布“上课”的事件,这个工具的底层是如何实现的?SimpleApplicationEventMulticaster
spring提供的一个默认的广播器,我们可以给它提供一个线程池这样监听器就可以在新的线程中执行任务了。
/**
* 多线程版发布器
*/
@Bean
ApplicationEventMulticaster applicationEventMulticaster(ThreadPoolTaskExecutor threadPoolTaskExecutor) {
SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
multicaster.setTaskExecutor(threadPoolTaskExecutor);
return multicaster;
}
自定义广播器
AbstractEventMulticaster
一个抽象类对方法做空实现,AppEventMulticaster
真正的自定义的广播器,
自定义广播器我们只需要关心两个方法addApplicationListenerBean
和multicastEvent
。
-
addApplicationListenerBean
方法寻找并添加所有listener -
multicastEvent
遍历所有的listener调用listener的onApplicationEvent
方法。
public abstract class AbstractEventMulticaster implements ApplicationEventMulticaster {
// 对所有的方法做空实现
}
/**
* Multicaster会把每一个被观察者发布的每一个事件广播给所有的观察者,而观察者只需要处理自己指定的那一个事件(或指定的事件的子类事件),
* 所以使用GenericApplicationListener对原生的listener(实现了ApplicationListener接口的事件)做一层包装。这层包装引入了一个新的方法,
* 方法就是supportsEventType,作用是判断当前的监听器是否可以处理被观察者发布的事件。
*/
public class AppEventMulticaster extends AbstractEventMulticaster {
@Autowired
ConfigurableApplicationContext ac;
@Autowired
ThreadPoolTaskExecutor threadPoolTaskExecutor;
static final Set<GenericApplicationListener> listenerSet = new HashSet<>();
/**
* 第一步:从spring容器获取所有实现了ApplicationListener的bean(listener观察者),
* 使用GenericApplicationListener包listener,然后将包装后的listener放入set集合
*/
@Override
public void addApplicationListenerBean(String listenerBeanName) {
// 这些bean是自己实现的(一些是spring框架内部用的)
ApplicationListener listenerBean = ac.getBean(listenerBeanName, ApplicationListener.class);
// 包装listener
GenericApplicationListener genericApplicationListener = new GenericApplicationListener() {
/**
* 第三步:判断listenerBean是否可以处理这个事件
* (我们自定义的listenerBean只处理我们指定的事件(泛型中指定),而multicaster不会区分事件,因此一定需要一个support方法)
* 获取listenerBean的泛型(事件),这个泛型需要是发布的事件的父类(或泛型和发布的事件是同一个类)
* @param publishEvent 发布的事件
*/
@Override
public boolean supportsEventType(ResolvableType publishEvent) {
// 获取listener所有实现的接口
ResolvableType[] interfaces = ResolvableType.forClass(listenerBean.getClass()).getInterfaces();
for (ResolvableType anInterface : interfaces) {
// 获取接口的泛型 genericType。
ResolvableType[] generics = anInterface.getGenerics();
for (ResolvableType genericType : generics) {
// isAssignableFrom: 泛型需要是发布的事件的父类(或泛型和发布的事件是同一个类),满足这个条件返回true
if (genericType.isAssignableFrom(publishEvent)) {
return true;
}
}
}
return false;
}
/**
* 第四步:第三步为true调用这个方法(第二步)
* 真正的发布事件,调用原生listener的onApplicationEvent方法
*/
@Override
public void onApplicationEvent(ApplicationEvent event) {
threadPoolTaskExecutor.submit(() -> listenerBean.onApplicationEvent(event));
}
};
listenerSet.add(genericApplicationListener);
}
/**
* 第二步:被观察者发布事件了,
* Multicaster遍历所有包装后的listener,并调用supportsEventType方法确认当前的监听器是否可以处理这个事件,
* 如果监听器支持处理这个事件就调用onApplicationEvent方法。
*
* @param event the event to multicast
* @param eventType the type of event (can be {@code null})
*/
@Override
public void multicastEvent(ApplicationEvent event, ResolvableType eventType) {
for (GenericApplicationListener applicationListener : listenerSet) {
if (applicationListener.supportsEventType(ResolvableType.forClass(event.getClass()))) {
applicationListener.onApplicationEvent(event);
}
}
}
}