IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 设计模式(四)—观察者模式在java和Spring中的应用 -> 正文阅读

[Java知识库]设计模式(四)—观察者模式在java和Spring中的应用

最近面试不止一次提到了观察者模式,所以再次学习这个神奇的模式

一、观察者模式基本概况

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提供的接口和类实现项目中使用观察者模式
在这里插入图片描述
先说下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实现了事件—监听机制,使用的步骤有如下几步

  • 1.定义事件:继承ApplicationEvent类,实现方法传入事件源。由事件源产生事件
  • 2.发布事件:使用Spring的IOC容器ApplicationContext的publishEvent方法发布事件
  • 3.监听事件:实现ApplictionListener接口重写方法即可实现自定义监听器

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

先将上述过程画成一幅图,然后展开来解释各个步骤,相信我你能看懂
在这里插入图片描述
通过上面的流程图,回答下面几个问题

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

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

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

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接口的子类
在这里插入图片描述
该接口主要两个职责,维护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.publishEvent→SimpleApplicaitonEventMulticaster.multicastEvent→SimpleApplicaitonEventMulticaster.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);
	}
}

四、最后一张图总结

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

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-10-12 23:18:12  更:2021-10-12 23:18:22 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 22:28:09-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码