观察者模式

观察者模式是一个重要的设计模式,很多框架都使用了该模式。

观察者模式

观察者模式

  • Subject(或者叫做 Observable)
    被观察者,一般是一个抽象类,持有观察者的集合,除了自身的方法外,还有管理观察者和通知观察者的方法

  • Observer(观察者)
    观察者接口

下面使用代码来演示该模式。

不使用 JDK 提供的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.nekolr.util;

/**
* 观察者接口
*
* @author nekolr
*/
public interface Observer {

/**
* 更新状态
*/
void update();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.nekolr.util;

import java.util.Vector;

/**
* 主题(被观察者)
*
* @author nekolr
*/
public abstract class Subject {

/**
* 观察者集合
*/
private Vector<Observer> observers = new Vector<>();


/**
* 添加一个观察者
*
* @param observer
*/
public void addObserver(Observer observer) {
observers.add(observer);
}

/**
* 删除一个观察者
*
* @param observer
*/
public void removeObserver(Observer observer) {
observers.remove(observer);
}

/**
* 通知观察者
*/
public void notifyObservers() {
for (Observer observer : observers) {
observer.update();
}
}

/**
* 状态变更方法
*/
protected abstract void change();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.nekolr.util;

/**
* 主题的具体实现类
*
* @author nekolr
*/
public class ConcreteSubject extends Subject {


@Override
protected void change() {
System.out.println("我的状态改变了");

// 通知观察者
this.notifyObservers();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.nekolr.util;

public class TestObserver {

public static void main(String[] args) {
// 创建被观察者
Subject subject = new ConcreteSubject();

Observer observer1 = () -> System.out.println("observer1 收到了");

Observer observer2 = () -> System.out.println("observer2 收到了");

subject.addObserver(observer1);
subject.addObserver(observer2);

// 改变状态
subject.change();
}
}

使用 JDK 的实现

其实,JDK 已经提供了被观察者(java.util.Observable)和观察者(java.util.Observer),下面根据 JDK 提供的类进行改造。

JDK 提供的观察者模式

需要注意的是,JDK 提供的被观察者只有一个改变状态的方法,我们可以定义一个 Subject 接口来声明一些自身的逻辑方法,具体的被观察者只需要实现该接口,并继承 java.util.Observable 类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.nekolr.util;

/**
* 主题(被观察者)接口
*
* @author nekolr
*/
public interface Subject {

/**
* 被观察者自身的方法
*/
void operation();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.nekolr.util;

/**
* 主题的具体实现类
*
* @author nekolr
*/
public class ConcreteSubject extends java.util.Observable implements Subject {

@Override
public void operation() {
System.out.println("自身的方法");

// 状态改变
super.setChanged();

// 通知所有的观察者
super.notifyObservers("我可通知你们了哦");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.nekolr.util;

public class TestObserver {

public static void main(String[] args) {
// 创建被观察者
ConcreteSubject subject = new ConcreteSubject();

java.util.Observer observer1 = (o, arg) ->
System.out.println("observer1 收到了 " + o.toString() + " 发过来的 " + arg);
java.util.Observer observer2 = (o, arg) ->
System.out.println("observer2 收到了 " + o.toString() + " 发过来的 " + arg);


subject.addObserver(observer1);
subject.addObserver(observer2);

// 改变状态
subject.operation();
}
}

事件监听模式

事件监听模式其实也是观察者模式的一种,但是实现要比一般的观察者模式复杂。

JDK 提供的事件监听模式

  • EventSource
    事件源。定义具体的事件,封装了(持有)监听者列表,能够添加和删除监听者,以及通知所有的监听者状态变更。

  • EventObject
    事件对象。封装了事件源以及事件相关的信息,在事件源与事件监听器之间传递信息。

  • EventListener
    事件监听器。注册在事件源上,进行事件处理。

JDK 也提供了事件监听模式需要的类和接口。EventListener 接口和 EventObject 类,事件源需要我们自己编写。下面用代码模拟该设计模式。

使用 JDK 的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package com.nekolr.util;

import java.util.Iterator;
import java.util.Vector;

/**
* 事件源
*/
public class EventSource {

/**
* 监听者列表
*/
private Vector<ChangeListener> changeListeners = new Vector<>();

/**
* 添加监听者
*
* @param listener
*/
public void addListener(ChangeListener listener) {
changeListeners.add(listener);
}

/**
* 单击事件
*/
public void onClick() {
ChangeEvent event = new ChangeEvent(this);
event.setStatus("click");
this.notifyListeners(event);
}

/**
* 双击事件
*/
public void onDoubleClick() {
ChangeEvent event = new ChangeEvent(this);
event.setStatus("double click");
this.notifyListeners(event);
}

/**
* 通知所有的监听者
*
* @param event
*/
private void notifyListeners(ChangeEvent event) {
for (Iterator<ChangeListener> iterator = changeListeners.iterator(); iterator.hasNext(); ) {
ChangeListener listener = iterator.next();
listener.update(event);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.nekolr.util;

import java.util.EventObject;

/**
* 事件对象
*/
public class ChangeEvent extends EventObject {

/**
* 状态
*/
private String status;


public ChangeEvent(EventSource source) {
super(source);
}

public String getStatus() {
return status;
}

public void setStatus(String status) {
this.status = status;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
package com.nekolr.util;

import java.util.EventListener;

/**
* 监听器
*/
public interface ChangeListener extends EventListener {

void update(ChangeEvent event);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.nekolr.util;

public class TestListener {

public static void main(String[] args) {

EventSource source = new EventSource();

source.addListener((event) -> System.out.println(event.getStatus()));

source.onClick();
source.onDoubleClick();
}
}

区别

虽然事件监听模式也是观察者模式的一种,但是它俩还是有区别的。

区别

观察者模式只需要两个角色,而事件监听模式则需要三个角色,其实是将主题拆分成了事件源和事件对象。事件源需要经过包装,成为事件对象的属性后再传递给监听器。

实例

Spring 是如何使用的

Spring 作为一个 Java 界流行的框架,同样使用了该模式。比如 Spring 的 ApplicationListenerApplicationEvent

( 该文涉及的 Spring 的源代码对应的 Spring 版本为 4.3.13.RELEASE )

1
2
3
4
5
6
7
8
9
10
11
12
13
package org.springframework.context;

import java.util.EventListener;

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);

}

ApplicationListener 继承了 java.util.EventListener,也就是说这是一个监听者接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package org.springframework.context;

import java.util.EventObject;

/**
* Class to be extended by all application events. Abstract as it
* doesn't make sense for generic events to be published directly.
*
* @author Rod Johnson
* @author Juergen Hoeller
*/
public abstract class ApplicationEvent extends EventObject {

/** use serialVersionUID from Spring 1.2 for interoperability */
private static final long serialVersionUID = 7099057708183571937L;

/** System time when the event happened */
private final long timestamp;


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


/**
* Return the system time in milliseconds when the event happened.
*/
public final long getTimestamp() {
return this.timestamp;
}

}

ApplicationEvent 继承了 EventObject,因此它是一个事件对象。

很明显的,ApplicationListener 继承了 EventListener,而 ApplicationEvent 继承了 EventObject,因此我们还缺少一个 EventSource(事件源),通过跟踪代码发现这个事件源是 ApplicationContext,即容器上下文(或者理解为容器本身)。我判断事件源是它的原因是 ApplicationContextEvent 这个类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package org.springframework.context.event;

import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;

/**
* Base class for events raised for an {@code ApplicationContext}.
*
* @author Juergen Hoeller
* @since 2.5
*/
@SuppressWarnings("serial")
public abstract class ApplicationContextEvent extends ApplicationEvent {

/**
* 这里的参数为 ApplicationContext,也就是将 ApplicationContext 作为 source
*/
public ApplicationContextEvent(ApplicationContext source) {
super(source);
}

/**
* Get the {@code ApplicationContext} that the event was raised for.
*/
public final ApplicationContext getApplicationContext() {
return (ApplicationContext) getSource();
}

}

Spring 容器(IOC 容器)有一个重要的方法 refreshAbstractApplicationContext 类中,该方法用于加载或刷新配置(XML、配置文件等)的 Bean。执行该方法,如果已经有容器存在,则会销毁容器和已经存在的实例并重新实例化,类似于重启。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();

// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);

try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);

// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);

// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);

// Initialize message source for this context.
initMessageSource();

// Initialize event multicaster for this context.
initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses.
onRefresh();

// Check for listener beans and register them.
registerListeners();

// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
finishRefresh();
}

catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}

// Destroy already created singletons to avoid dangling resources.
destroyBeans();

// Reset 'active' flag.
cancelRefresh(ex);

// Propagate exception to caller.
throw ex;
}

finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}

refresh 方法里有一个方法 finishRefresh

1
2
3
4
5
6
7
8
9
10
11
12
13
protected void finishRefresh() {
// Initialize lifecycle processor for this context.
initLifecycleProcessor();

// Propagate refresh to lifecycle processor first.
getLifecycleProcessor().onRefresh();

// 这里发布事件对象 ContextRefreshedEvent
publishEvent(new ContextRefreshedEvent(this));

// Participate in LiveBeansView MBean, if active.
LiveBeansView.registerApplicationContext(this);
}

从大意上看,这里的代码:

publishEvent(new ContextRefreshedEvent(this)) 是将事件源 ApplicationContext 封装成事件对象 ContextRefreshedEvent 发布。与我们设想的通知监听者不太一样,继续看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
protected void publishEvent(Object event, ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Publishing event in " + getDisplayName() + ": " + event);
}

// Decorate event as an ApplicationEvent if necessary
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}
else {
applicationEvent = new PayloadApplicationEvent<Object>(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent) applicationEvent).getResolvableType();
}
}

// Multicast right now if possible - or lazily once the multicaster is initialized
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else {
// 这里是重点
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}

// Publish event via parent context as well...
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
}
else {
this.parent.publishEvent(event);
}
}
}

publishEvent 方法中执行了这样一段代码:

getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType),这段代码大意是获取容器事件的广播器来广播事件,multicastEvent 方法在 SimpleApplicationEventMulticaster 类中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
Executor executor = getTaskExecutor();
if (executor != null) {
executor.execute(new Runnable() {
@Override
public void run() {
invokeListener(listener, event);
}
});
}
else {
invokeListener(listener, event);
}
}
}

这里很明显的发现,使用 getApplicationListeners 方法来获取所有的监听者,遍历执行 invokeListener 方法,重点是 invokeListener 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
ErrorHandler errorHandler = getErrorHandler();
if (errorHandler != null) {
try {
doInvokeListener(listener, event);
}
catch (Throwable err) {
errorHandler.handleError(err);
}
}
else {
doInvokeListener(listener, event);
}
}

@SuppressWarnings({"unchecked", "rawtypes"})
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
String msg = ex.getMessage();
if (msg == null || msg.startsWith(event.getClass().getName())) {
// Possibly a lambda-defined listener which we could not resolve the generic event type for
Log logger = LogFactory.getLog(getClass());
if (logger.isDebugEnabled()) {
logger.debug("Non-matching event type for listener: " + listener, ex);
}
}
else {
throw ex;
}
}
}

doInvokeListener 方法调用了 listener 的 onApplicationEvent 方法,传入 event 事件对象来通知监听者。同样的,我们在 AbstractApplicationContext 类中找到了容器启动和关闭时执行的事件。

1
2
3
4
5
6
7
8
9
10
11
@Override
public void start() {
getLifecycleProcessor().start();
publishEvent(new ContextStartedEvent(this));
}

@Override
public void stop() {
getLifecycleProcessor().stop();
publishEvent(new ContextStoppedEvent(this));
}

看到这里我有个疑惑,ApplicationContext 作为事件源,应该注册监听者啊,在哪呢?原来在 refresh 方法里,registerListeners 方法就是了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

protected void registerListeners() {
// Register statically specified listeners first.
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);
}

// Publish early application events now that we finally have a multicaster...
Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
this.earlyApplicationEvents = null;
if (earlyEventsToProcess != null) {
for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
getApplicationEventMulticaster().multicastEvent(earlyEvent);
}
}
}

小栗子

下面使用一个小栗子来说明如何使用 Spring 的事件处理。

1
2
3
4
5
6
7
8
9
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextStartedEvent;

public class MyStartedListener implements ApplicationListener<ContextStartedEvent> {
@Override
public void onApplicationEvent(ContextStartedEvent event) {
System.out.println("------------------- Spring started ---------------------");
}
}
1
2
3
4
5
6
7
8
9
10
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextStoppedEvent;


public class MyStoppedListener implements ApplicationListener<ContextStoppedEvent> {
@Override
public void onApplicationEvent(ContextStoppedEvent event) {
System.out.println("------------------- Spring stopped ---------------------");
}
}
1
2
3
4
5
6
7
8
9
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;

public class MyRefreshedListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("------------------- Spring refreshed ---------------------");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {

public static void main(String[] args) {
// applicationContext 在 src/main/resources 目录下
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

context.start();

context.stop();
}
}
1
2
3
<bean id="myStartedListener" class="MyStartedListener"></bean>
<bean id="myStoppedListener" class="MyStoppedListener"></bean>
<bean id="myRefreshedListener" class="MyRefreshedListener"></bean>

结果如下:

1
2
3
------------------- Spring refreshed ---------------------
------------------- Spring started ---------------------
------------------- Spring stopped ---------------------

通过 ClassPathXmlApplicationContext 来加载配置文件初始化容器上下文,在容器调用 start 方法时,事件 ContextStartedEvent 发布,容器调用 stop 方法时,事件 ContextStoppedEvent 发布。但是常用的业务场景是我们需要在容器加载完成后执行一些操作,这时候我们常使用 ContextRefreshEvent 事件对象。但是在使用它的时候需要注意,尤其是我们使用了 Spring 和 Spring MVC,因为父子容器的原因,系统中会存在两个容器,一个是 Root Application Context,另一个就是我们配置的 Spring MVC 的容器(作为 ROOT 的子容器)。这个时候 refresh 会被调用两次,同样监听器里的 onApplicationEvent 也会执行两次,避免这种情况就需要在自定义的监听器里修改代码:

1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;

public class MyRefreshedListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 没有父容器,则为 ROOT 容器
if (event.getApplicationContext().getParent() == null) {
System.out.println("------------------- Spring refreshed ---------------------");
}
}
}

Spring 中的事件对象:

事件对象 说明
ContextRefreshedEvent ApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext 接口中使用 refresh() 方法来发生。
ContextStartedEvent 当使用 ConfigurableApplicationContext 接口中的 start() 方法启动 ApplicationContext 时,该事件被发布。你可以调查你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序。
ContextStoppedEvent 当使用 ConfigurableApplicationContext 接口中的 stop() 方法停止 ApplicationContext 时,发布这个事件。你可以在接受到这个事件后做必要的清理的工作。
ContextClosedEvent 当使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启。