前言
整个 9 月份基本是在花式加班中疲劳度过的, 工作中进步不少, 自学进度却放慢了. 十一长假, 处理完家里的事情后提前一天来了上海, 收拢一下思绪, 准备迎接下一阶段的工作, 学习. 不知不觉, 2019 年只剩下了不到三个月, 来自时间的压迫感无时无刻不在, 需要抓紧最后的机会, 利用好这三四个月的时间.
闲话少叙, 这一次我打算将观察者设计模式梳理一下, 从 JDK 中的设计, 到 Spring 中的应用, 都会涉及到. 心得以及感悟都是一家之言, 如有不恰当之处, 还望各位道友指正!
一, 结合案例分析 java 中的观察者模式
首先看一下 java 中已经定义好了的观察者类 (Observer), 被观察者类(Observable) 的结构:
- // 观察者类
- public interface Observer {
- // 此方法用于定义观察者观察到变化后发生的行为
- // 第一个参数是被观察者; 第二个参数是一个可变对象, 方便动态传递某些信息
- void update(Observable o, Object arg);
- }
- public class Observable {
- // 变动标识, 用于判断被观察者是否有变化
- private boolean changed = false;
- // 存放观察者
- private Vector<Observer> obs;
- public Observable() {
- obs = new Vector<>();
- }
- public synchronized void addObserver(Observer o) {
- if (o == null)
- throw new NullPointerException();
- if (!obs.contains(o)) {
- obs.addElement(o);
- }
- }
- public synchronized void deleteObserver(Observer o) {
- obs.removeElement(o);
- }
- public void notifyObservers() {
- notifyObservers(null);
- }
- // 通知被观察者发生了变化, 循环调用 update 方法 ☆
- public void notifyObservers(Object arg) {
- Object[] arrLocal;
- synchronized (this) {
- if (!changed)
- return;
- arrLocal = obs.toArray();
- clearChanged();
- }
- for (int i = arrLocal.length-1; i>=0; i--)
- ((Observer)arrLocal[i]).update(this, arg);
- }
- public synchronized void deleteObservers() {
- obs.removeAllElements();
- }
- // 将改变状态设置成已改变
- protected synchronized void setChanged() {
- changed = true;
- }
- protected synchronized void clearChanged() {
- changed = false;
- }
- public synchronized boolean hasChanged() {
- return changed;
- }
- public synchronized int countObservers() {
- return obs.size();
- }
- }
可见被观察者类中维护了一个观察者的列表, 在发生变化通知观察者时是循环列表, 调用每个观察者的 update 方法, 具体每个观察者是如何做的, 取决于其 update 方法.
知道了 java 中的观察者和被观察者类, 我们要如何使用呢? 且看下面的例子.
十一回来的路上看了好几篇半佛仙人的文章, 略有感悟, 于是此次就用仙人的案例作为主体进行设计. 仙人是一个风控出身的 "社会工程学专家", 热衷于写文章揭露社会上的沙雕事件, 违法事件. 所以此处观察者就是仙人, 而被观察者则是违法事件, 下面是定义出来的两个类:
- /**
- * 仙人是观察者, 实现 Observer 观察者接口
- */
- public class CelestialBeing implements Observer {
- @Override
- public void update(Observable o, Object arg) {
- writeToPen();
- }
- // 写文章揭露(也就是喷)
- private void writeToPen(){
- System.out.println("写文章批斗, 吊打");
- }
- }
- /**
- * 违法事件是被观察者, 一旦出现变化, 会被半佛仙人观察到
- */
- public class IllegalThing extends Observable {
- public void change() {
- System.out.println("出现了违法事件");
- super.setChanged();
- }
- }
下面是测试类:
- public class TestClient {
- public static void main(String[] args) {
- IllegalThing illegalThing = new IllegalThing();
- /**
- * 观察者模式的关键点: 被观察者持有观察者
- */
- illegalThing.addObserver(new CelestialBeing());
- // 有改变
- illegalThing.change();
- // 通知所有观察者
- illegalThing.notifyObservers();
- }
- }
运行结果:
出现了违法事件
写文章批斗, 吊打
可以看到, 由于被观察者类 Observable 自身已经维护好了观察者列表, 所以我们的被观察者类不需要做太多的事情, 只需要将 setChanged 方法暴露出去即可. 观察者模式的关键, 就是上面注释中提到的: 被观察者持有观察者列表. 只要注意了这一点, 相信大家在实际场景中使用时便不会用错.
二, 看看观察者模式在 Spring 中的应用
1, 在 Spring 容器的某个阶段触发事件
如果我想在 Spring 容器 refresh 之后触发某些特定逻辑, 那么定义一个这样的类就可以:
- @Component
- public class MySpringListener implements ApplicationListener<ContextRefreshedEvent> {
- @Override
- public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
- System.out.println("MySpringObserver 执行了");
- // 执行自定义逻辑
- }
- }
能如此方便的做到, 主要是因为 Spring 帮我们创建好了 refresh 的事件, 在 Spring 容器过程中类似的事件有以下几个:
2, 自定义的事件触发
如果我们想在某些特定的时机触发一个自定义的事件, 比如在发邮件时触发一个事件监听, 那么在 Spring 中要怎么做呢?
首先定义一个邮件事件:
- public class MySpringEmailEvent extends ApplicationEvent {
- public MySpringEmailEvent(Object source) {
- super(source);
- }
- }
- @Component
- public class MySpringEmailListener implements ApplicationListener<MySpringEmailEvent> {
- @Override
- public void onApplicationEvent(MySpringEmailEvent mySpringEmailEvent) {
- System.out.println("MySpringEmailListener 执行了");
- // 执行自定义逻辑
- }
- }
- @Component
- public class MyEventTrigger {
- @Autowired
- private ApplicationContext applicationContext;
- // 触发事件
- public void sendEmail () {
- applicationContext.publishEvent(new MySpringEmailEvent(applicationContext));
- }
- }
测试类:
- public class SpringClient {
- public static void main(String[] args) {
- AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
- MyEventTrigger bean = applicationContext.getBean(MyEventTrigger.class);
- bean.sendEmail();
- }
- }
执行结果:
1 MySpringEmailListener 执行了
看到这里, 相信很多道友会有疑问, Spring 是如何将事件和 listener 关联在一起的呢? 其实内部是通过 ApplicationEventMulticaster 接口, 它是如何实现的呢? 且看下回分析...
posted on 2019-10-07 14:30 张曾经 阅读(...) 评论(...) 编辑 收藏
来源: https://www.cnblogs.com/zzq6032010/p/11605335.html