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

 更新时间:2021年10月14日 11:08:56   作者:商俊帅  
这篇文章主要介绍了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实现了事件—监听机制,使用的步骤有如下几步

  • 定义事件:继承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如何自定义事件、监听器以及发布事件和通知监听者的原理,大家可以自己梳理下。

相关文章

  • java整数(秒数)转换为时分秒格式的示例

    java整数(秒数)转换为时分秒格式的示例

    这篇文章主要介绍了java整数(秒数)转换为时分秒格式的示例,需要的朋友可以参考下
    2014-04-04
  • Java获得当前时间前指定几个小时具体时间的方法示例

    Java获得当前时间前指定几个小时具体时间的方法示例

    这篇文章主要介绍了Java获得当前时间前指定几个小时具体时间的方法,涉及java使用Calendar针对日期时间的相关运算与转换操作技巧,需要的朋友可以参考下
    2017-08-08
  • 基于Java随机生成手机短信验证码的实例代码

    基于Java随机生成手机短信验证码的实例代码

    这篇文章主要介绍了Java随机生成手机短信验证码的实例代码,代码分为哦简单版和复杂版,需要的朋友可以参考下
    2019-04-04
  • 详解Java 自动装箱与自动拆箱

    详解Java 自动装箱与自动拆箱

    这篇文章主要介绍了Java 自动装箱与自动拆箱的相关资料,帮助大家更好的理解和学习Java,感兴趣的朋友可以了解下
    2020-09-09
  • springmvc Rest风格介绍及实现代码示例

    springmvc Rest风格介绍及实现代码示例

    这篇文章主要介绍了springmvc Rest风格介绍及实现代码示例,rest风格简洁,分享了HiddenHttpMethodFilter 的源码,通过Spring4.0实现rest风格源码及简单错误分析,具有一定参考价值,需要的朋友可以了解下。
    2017-11-11
  • Springboot基于enable模块驱动的实现

    Springboot基于enable模块驱动的实现

    这篇文章主要介绍了Springboot基于enable模块驱动的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-08-08
  • Java开发反射机制的实战经验总结

    Java开发反射机制的实战经验总结

    反射是java中一种强大的工具,能够使我们很方便的创建灵活的代码,这些代码可以再运行时装配,无需在组件之间进行源代码链接,但是反射使用不当会成本很高,这篇文章主要给大家介绍了关于Java开发反射机制的相关资料,需要的朋友可以参考下
    2021-07-07
  • Struts2的输入校验实例代码

    Struts2的输入校验实例代码

    这篇文章主要介绍了Struts2的输入校验实例代码,非常不错,具有参考借鉴价值, 需要的朋友可以参考下
    2017-03-03
  • 解决IDEA的Terminal中文乱码问题

    解决IDEA的Terminal中文乱码问题

    这篇文章主要介绍了解决IDEA的Terminal中文乱码问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-07-07
  • java编程枚举类型那些事!枚举类型定义和重写枚举的方法

    java编程枚举类型那些事!枚举类型定义和重写枚举的方法

    本文主要介绍了枚举类型的有关内容,涉及简单的枚举类型定义,以及枚举类型的值在Java中的定义方法,具有一定参考价值,需要的朋友可以了解下。
    2017-10-10

最新评论