java和Spring中观察者模式的应用详解

作者:商俊帅 时间:2023-04-21 00:16:17 

一、观察者模式基本概况

1.概念

观察者模式(Observer Design Pattern)也被称为发布订阅模式(Publish-Subcribe Design Pattern)。定义如下

Define a one-to-many dependency between objects so that when one object changes state,all its dependents are notified and update automatically。

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有观察者对象,使它们能够自动更新自己。

2.作用

参考设计模式之美的一段总结

回到本质,设计模式要干的事情就是解耦。创建型模式是将创建对象和使用对象解耦,结构型模式是将不同功能代码解耦,行为型模式是将不同的行为代码解耦,具体到观察者模式,是将观察者和被观察者代码解耦。

3.实现方式

观察者模式在不同的场景有不同的实现方式,自然也有不同的名字。如Publisher-Subscriber、Producer-Consumer、Dispatcher-Listener,等等。

有哪些不同实现方式呢?同步阻塞方式异步非阻塞方式进程内实现方式跨进程实现方式

同步阻塞方式是经典的实现方式,是说在主题通知观察者和观察者执行自己的逻辑是由同一个线程完成的,比如下面要说的java中Observer接口和Observable类,后有UML图


public void notifyObservers(Object arg) {
   for (int i = arrLocal.length-1; i>=0; i--){
   //使用了for循环同步通知观察者
   ((Observer)arrLocal[i]).update(this, arg);
   }
}

异步非阻塞方式是在通知观察者和观察者执行自己逻辑不是同一个线程,如下代码


public void notifyObservers(Object arg) {
   for (int i = arrLocal.length-1; i>=0; i--){
   //使用了开启新线程方式异步通知观察者
   new Thread(()->{
   ((Observer)arrLocal[i]).update(this, arg);
}).start();
   }
}

上面都是在同一个进程中,还有跨进程的实现方式就是常用的MQ,消息队列

二、java实现两种观察者模式

1.Observer接口和Observable类

下面我们使用JDK提供的接口和类实现项目中使用观察者模式

java和Spring中观察者模式的应用详解

先说下Observer接口和Observable类

Observable类,被观察者。有三个组成部分,用Vector管理注册的观察者(Observer),并提供了增删、统计方法。一旦状态(changed)改变就通知(notifyObservers)观察者。notifyObservers方法会调用观察者(Observer)的update方法。

Observer接口就是一个观察者的标准,实现该接口并重写update方法就可以实现一个观察者。

具体的源码大家可以自己看下,比较简单,在java.util包下。

实现一个需求

当自定义被观察者data值=1时通知各个观察者执行update方法,应该如何做?

UML图已经画出来了,剩下就是写代码了。

代码如下


//自定义被观察者
public class MyObservable extends Observable {
   private int data;
   public void setData(int data) {
       this.data = data;
   }
   public void notifyObserver(){
   //当data等于1时通知观察者
       if (data==1){
           this.setChanged();
           super.notifyObservers();
       }
   }
}
//自定义观察者
public class MyObserver implements Observer {
   @Override
   public void update(Observable o, Object arg) {
       System.out.println("自定义观察者执行了.....");
   }
}
//测试类
public class PubSubTest {
   public static void main(String[] args) {
       MyObservable observable = new MyObservable();
       observable.addObserver(new MyObserver());
       observable.setData(1);
       observable.notifyObserver();
   }
}

2.EventObject和EventListener

JDK提供了EventObject类和EventListener接口定义了实现观察者模式的第二个标准,只是定义了标准没有提供默认实现,但是其他框架如Spring实现该方式,这个下面再说。

先看EventObject类

该类实现的功能就是定义一个事件,其中有一个最重要的属性source,叫事件源。含义是哪个类发出的事件。自定义事件时需要继承该类


public class EventObject implements java.io.Serializable {
   protected transient Object  source;
   public EventObject(Object source) {
       if (source == null)
           throw new IllegalArgumentException("null source");
       this.source = source;
   }
   public Object getSource() {
       return source;
   }
}

EventListener接口是一个空接口,自定义 * 需要继承该接口。

注意此时事件就是一个被观察者,监视器就是观察者。

三、Spring事件监听实战及原理

1.Spring如何使用EventObject和EventListener实现观察者?

在Spring中为自定义事件和自定义监听者,分别提供一个类和一个接口。

ApplicationEvent类继承了EventObject,用于在Spring环境下自定义事件


public abstract class ApplicationEvent extends EventObject {
/**
* Create a new {@code ApplicationEvent}.
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
*/
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}
}

ApplicationListener接口继承JDK的EventListener,用于在Spring环境下自定义监听者


@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
}

onApplicationEvent方法就是根据传过来的事件参数,监听是否是自己感兴趣的事件,然后执行方法体。

介绍完类和接口剩下的就是如何在Spring中如何自定义事件、如何发布事件、如何使用自定义 * 监听事件,下面举个例子

2.先实战—要先会用

实现一个需求:当调用一个类的方法完成时,该类发布事件,事件 * 监听该类的事件并执行的自己的方法逻辑

假设这个类是Request、发布的事件是ReuqestEvent、事件监听者是ReuqestListener。当调用Request的doRequest方法时,发布事件。模拟的代码如下


public class SpringEventTest {
   public static void main(String[] args) {
       ApplicationContext context=new AnnotationConfigApplicationContext("com.thinkcoder.parttern.behavioral.pubsub.spring");
       Request request = (Request) context.getBean("request");
       //调用方法,发布事件
       request.doRequest();
   }
}
//定义事件
class RequestEvent extends ApplicationEvent {
   public RequestEvent(Request source) {
       super(source);
   }
}
//发布事件
@Component
class Request{
   @Autowired
   private ApplicationContext applicationContext;
   public void doRequest(){
       System.out.println("调用Request类的doRequest方法发送一个请求");
       applicationContext.publishEvent(new RequestEvent(this));
   }
}
//监听事件
@Component
class RequestListener implements ApplicationListener<RequestEvent> {
   @Override
   public void onApplicationEvent(RequestEvent event) {
       System.out.println("监听到RequestEvent事件,执行方法");
   }
}
//打印的日志
调用Request类的doRequest方法发送一个请求
监听到RequestEvent事件,执行方法

上面我们依靠spring实现了事件—监听机制,使用的步骤有如下几步

  • 定义事件:继承ApplicationEvent类,实现方法传入事件源。由事件源产生事件

  • 发布事件:使用Spring的IOC容器ApplicationContext的publishEvent方法发布事件

  • 监听事件:实现ApplictionListener接口重写方法即可实现自定义 *

3.会原理—搞清楚为什么会这样

先将上述过程画成一幅图,然后展开来解释各个步骤,相信我你能看懂

java和Spring中观察者模式的应用详解

通过上面的流程图,回答下面几个问题

1. * 什么时候注册到IOC容器中?

注册的开始逻辑是在AbstractApplicationContext类的refresh方法,该方法包含了整个IOC容器初始化所有方法。其中有一个registerListeners()方法就是注册系统监听者(spring自带的)和自定义 * 的。

看registerListeners的关键方法体,其中的两个方法addApplicationListeneraddApplicationListenerBean,从方法可以看出是添加监听者。


for (ApplicationListener<?> listener : getApplicationListeners()) {
getApplicationEventMulticaster().addApplicationListener(listener);
}
// Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let post-processors apply to them!
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
for (String listenerBeanName : listenerBeanNames) {
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
}

那么最后将监听者放到哪里了呢?就是ApplicationEventMulticaster接口的子类

java和Spring中观察者模式的应用详解

该接口主要两个职责,维护ApplicationListener相关类和发布事件。

实现是在默认实现类AbstractApplicationEventMulticaster,最后将Listener放到了内部类ListenerRetriever两个set集合中


private class ListenerRetriever {
public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();
public final Set<String> applicationListenerBeans = new LinkedHashSet<>();

ListenerRetriever又被称为 * 注册表。

2.Spring如何发布的事件并通知的监听者?

该问题开始是在ApplicationContext.publishEvent的方法,该方法调用路线

AbstractApplicationContext.publishEventSimpleApplicaitonEventMulticaster.multicastEventSimpleApplicaitonEventMulticaster.invokeListener→SimpleApplicaitonEventMulticaster.doInvokeListener→调用系统及自定义listener的onApplicationEvent方法,这个就是发布事件并通知的调用路线。

这个注意的有两个方法

multicastEvent方法,该方法有两种方式调用invokeListener,通过线程池和直接调用,进一步说就是通过异步和同步两种方式调用


public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}

最后看doInvokeListener方法


private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
//直接调用了listener接口的onApplicationEvent方法
listener.onApplicationEvent(event);
}
}

四、最后一张图总结

java和Spring中观察者模式的应用详解

上图包含了在Spring如何自定义事件、 * 以及发布事件和通知监听者的原理,大家可以自己梳理下。

来源:https://blog.csdn.net/shang_0122/article/details/120679734

标签:java,Spring,观察者,模式,应用
0
投稿

猜你喜欢

  • 彻底搞定堆排序:二叉堆

    2022-12-06 23:24:15
  • 详解C# 匿名对象(匿名类型)、var、动态类型 dynamic

    2022-03-26 18:43:02
  • winform绑定快捷键的方法

    2023-12-10 22:16:04
  • Java中如何动态创建接口的实现方法

    2023-11-25 15:13:02
  • Spring注解驱动开发实现属性赋值

    2023-05-07 04:40:22
  • 改进c# 代码的五个技巧(一)

    2021-07-17 23:49:30
  • Spring Boot 工程的创建和运行(图文)

    2022-06-26 15:39:49
  • C语言实现的猴子分桃问题算法解决方案

    2022-10-19 19:03:32
  • Java使用easyExcel导出excel数据案例

    2022-02-21 19:39:27
  • 详解Android如何实现阴影效果

    2022-04-14 13:11:16
  • Android巧用DecorView实现对话框功能

    2022-07-14 14:03:40
  • Spring Boot实现发送邮件

    2023-11-08 22:19:40
  • android实现程序自动升级到安装示例分享(下载android程序安装包)

    2023-08-12 09:37:19
  • Java 判断字符为中文实例代码(超管用)

    2021-08-24 13:18:31
  • Springmvc发送json数据转Java对象接收

    2023-07-07 16:26:16
  • Java实现分页查询功能

    2023-03-03 14:30:19
  • java swing实现简单计算器界面

    2021-11-09 12:47:05
  • c# 自定义泛型链表类的详解

    2022-07-27 23:58:46
  • C#调用带结构体指针Dll的方法

    2022-06-14 19:55:32
  • java字符串常用操作方法(查找、截取、分割)

    2023-11-29 03:21:13
  • asp之家 软件编程 m.aspxhome.com