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

目录
  • 一、观察者模式基本概况
    • 1.概念
    • 2.作用
    • 3.实现方式
  • 二、java实现两种观察者模式
    • 1.Observer接口和Observable类
    • 2.EventObject和EventListener
  • 三、Spring事件监听实战及原理
    • 1.Spring如何使用EventObject和EventListener实现观察者?
    • 2.先实战—要先会用
    • 3.会原理—搞清楚为什么会这样
  • 四、最后一张图总结

一、观察者模式基本概况

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实现了事件—监听机制,使用的步骤有如下几步

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

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

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

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

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接口的子类

该接口主要两个职责,维护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);
	}
}

四、最后一张图总结

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

(0)

相关推荐

  • Java设计模式之java观察者模式详解

    目录 引言 介绍 角色 原理类图 微信订阅号的案例 总结 优点 缺点 适用场景 观察者模式的典型应用 JDK 提供的观察者接口 Guava EventBus 中的观察者模式 Spring ApplicationContext 事件机制中的观察者模式 参考文章 总结 引言 观察者模式是设计模式中的 "超级模式",其应用随处可见,我们以微信公众号为例. 微信公众号有服务号.订阅号和企业号之分.当我们在公众号上发布一篇博文推送时,订阅的用户都能够在我发布推送之后及时接收到推送,即可方便地在手

  • Java设计模式之观察者模式(Observer模式)

    一.观察者模式是什么? 当对象间存在一对多关系时,则使用观察者模式(Observer Pattern).当一个对象被修改时,则会自动通知依赖它的对象.观察者模式属于行为型模式. 人话: 就像微信公众号推送消息,订阅的人能收到,没订阅的收不到,还可以取消/添加订阅 二.模式分析 2.1 四个角色 抽象主题(抽象被观察者角色):也就是一个抽象主题,它把所有对观察者对象的引用保存在一个集合中,每个主题都可以有任意数量的观察者.抽象主题提供一个接口,可以增加和删除观察者角色.一般用一个抽象类和接口来实现

  • java设计模式之观察者模式

    观察者模式又称发布-订阅(Publish/Subscribe)模式,定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象.这个主题对象在状态发生变化时,会通知所有观察者对象,使他们能够自动更新自己.将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相关对象间的一致性.我们不希望为了维持一致性而使各类紧密耦合,这样会给维护.扩展和复用都带来不便.观察者模式所做的工作其实就是在解除耦合,让耦合的双方都依赖于抽象,而不是依赖于具体. 观察者模式是实际中应用比较广泛的模

  • java设计模式之观察者模式学习

    1.什么是观察者模式 简单情形:有A.B.C.D等四个独立的对象,其中B.C.D这三个对象想在A对象发生改变的第一时间知道这种改变,以便做出相应的响应或者对策. 上面的这种情形,就是观察者模式. 当然可以有多个观察者,多个被观察者. 观察者与被观察者也不是对立的,一个对象可以观察其他对象,也可以被其他对象观察. 2.观察者模式的应用 为了更好的理解什么是观察者模式,下面我举一些可能用到该模式的情形或例子: (1)周期性任务.比如linux中的周期性任务命令crontab命令,win7下的定时关机

  • Java通俗易懂系列设计模式之观察者模式

    介绍 观察者模式是行为设计模式之一.当您对对象的状态感兴趣并希望在有任何更改时收到通知时,观察者设计模式非常有用.在观察者模式中,监视另一个对象状态的对象称为Observer,正在被监视的对象称为Subject. 根据GoF,观察者设计模式的意图是; 定义对象之间的一对多依赖关系,以便当一个对象更改状态时,将自动通知和更新其所有依赖项. Subject包含一个观察者列表,用于通知其状态的任何变化,因此它应该提供观察者可以注册和注销自己的方法.Subject还包含一种方法,用于通知所有观察者任何更

  • java 在观察者模式中使用泛型T的实例

    被观察者 public class Observable<T> { List<Observer> observers = new ArrayList<Observer>(); boolean changed = false; /** * Adds the specified observer to the list of observers. If it is already * registered, it is not added a second time. *

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

    目录 一.观察者模式基本概况 1.概念 2.作用 3.实现方式 二.java实现两种观察者模式 1.Observer接口和Observable类 2.EventObject和EventListener 三.Spring事件监听实战及原理 1.Spring如何使用EventObject和EventListener实现观察者? 2.先实战-要先会用 3.会原理-搞清楚为什么会这样 四.最后一张图总结 一.观察者模式基本概况 1.概念 观察者模式(Observer Design Pattern)也被称

  • Java 自定义Spring框架与核心功能详解

    目录 Spring核心功能结构 核心容器 spring-beans和spring-core模块 spring-context模块 spring-context-support模块 spring-context-indexer模块 spring-expression模块 AOP和设备支持 数据访问与集成 Web组件 通信报文 集成测试 bean概述 在上一讲中,我们对Spring的基本使用进行了一个简单的回顾,接下来,我们就来看一下Spring核心功能结构. Spring核心功能结构 Spring

  • java 查找list中重复数据实例详解

    java 查找list中重复数据实例详解 需求: 查找一个List集合中所有重复的数据,重复的数据可能不止一堆,比如:aa, bb, aa, bb, cc , dd, aa这样的数据.如果有重复数据,则给这些重复数据加上编号,上述数据改为:aa1, bb1, aa2, bb2, cc, dd. 算法如下: public static void same(List<String> list) { String [] indexArr ; Map<String, String> map

  • Spring中MVC模块代码详解

    SpringMVC的Controller用于处理用户的请求.Controller相当于Struts1里的Action,他们的实现机制.运行原理都类似 Controller是个接口,一般直接继承AbstrcatController,并实现handleRequestInternal方法.handleRequestInternal方法相当于Struts1的execute方法 import org.springframework.web.servlet.ModelAndView; import org.

  • Spring中BeanFactory解析bean详解

    在该文中来讲讲Spring框架中BeanFactory解析bean的过程,该文之前在小编原文中有发表过,先来看一个在Spring中一个基本的bean定义与使用. package bean; public class TestBean { private String beanName = "beanName"; public String getBeanName() { return beanName; } public void setBeanName(String beanName

  • Spring中ResponseBodyAdvice的使用详解

    目录 1 ResponseBodyAdvice的简介 2 ResponseBodyAdvice的使用 1 准备一个SpringBoot项目环境 3 添加一个返回包装类 4 添加控制类 5 接口测试 ResponseBodyAdvice可以在注解@ResponseBody将返回值处理成相应格式之前操作返回值.实现这个接口即可完成相应操作.可用于对response 数据的一些统一封装或者加密等操作 1 ResponseBodyAdvice的简介 ResponseBodyAdvice接口和之前记录的R

  • Java Spring中Quartz调度器详解及实例

    一.Quartz的特点 * 按作业类的继承方式来分,主要有以下两种: 1.作业类继承org.springframework.scheduling.quartz.QuartzJobBean类的方式 2.作业类不继承org.springframework.scheduling.quartz.QuartzJobBean类的方式 注:个人比较推崇第二种,因为这种方式下的作业类仍然是POJO. * 按任务调度的触发时机来分,主要有以下两种: 1.每隔指定时间则触发一次,对应的调度器为org.springf

  • Spring中统一异常处理示例详解

    前言 系统很多地方都会抛出异常, 而Java的异常体系目标就是与逻辑解耦,Spring提供了统一的异常处理注解,用户只需要在错误的时候提示信息即可 在具体的SSM项目开发中,由于Controller层为处于请求处理的最顶层,再往上就是框架代码的. 因此,肯定需要在Controller捕获所有异常,并且做适当处理,返回给前端一个友好的错误码. 不过,Controller一多,我们发现每个Controller里都有大量重复的.冗余的异常处理代码,很是啰嗦. 能否将这些重复的部分抽取出来,这样保证Co

  • Java在PDF中添加表格过程详解

    前言 本文将介绍通过Java编程在PDF文档中添加表格的方法.添加表格时,可设置表格边框.单元格对齐方式.单元格背景色.单元格合并.插入图片.设置行高.列宽.字体.字号等. 使用工具:Free Spire.PDF for Java (免费版) Jar文件获取及导入: 方法1:通过官网下载jar文件包.下载后,解压文件,将lib文件夹下的Spire.Pdf.jar文件导入Java程序. 方法2:通过maven仓库安装导入. Java 代码示例 Java代码 import com.spire.pdf

  • Java去除字符串中空格的方法详解

    昨天写了一个关于Excel文件处理的脚本,在字符串匹配功能上总是出现多余不正确的匹配,debug调试之后,发现一个坑. ------->代码中字符串使用了replaceAll()方法,去除了所有空格(其中包括:首尾空格.中间空格) 遂整理下java关于字符串去除空格的方法. 1.方法分类 str.trim(); //去掉首尾空格 str.replace(" ",""); //去除所有空格,包括首尾.中间 str.replaceAll(" "

随机推荐