Spring基于事件驱动模型的订阅发布模式代码实例详解
2017-10-30 10:38
1316 查看
事件驱动模型简介
事件驱动模型也就是我们常说的观察者,或者发布-订阅模型;理解它的几个关键点:
首先是一种对象间的一对多的关系;最简单的如交通信号灯,信号灯是目标(一方),行人注视着信号灯(多方);
当目标发送改变(发布),观察者(订阅者)就可以接收到改变;
观察者如何处理(如行人如何走,是快走/慢走/不走,目标不会管的),目标无需干涉;所以就松散耦合了它们之间的关系。
接下来先看一个用户注册的例子:
![](https://oscdn.geek-share.com/Uploads/Images/Content/201612/07/4bc4cf7dc0008b267eb3412ec453a062.png)
用户注册成功后,需要做这么多事:
1、加积分
2、发确认邮件
3、如果是游戏帐户,可能赠送游戏大礼包
4、索引用户数据
…………
问题:
UserService和其他Service耦合严重,增删功能比较麻烦;
有些功能可能需要调用第三方系统,如增加积分/索引用户,速度可能比较慢,此时需要异步支持;这个如果使用Spring,可以轻松解决,后边再介绍;
从如上例子可以看出,应该使用一个观察者来解耦这些Service之间的依赖关系,如图:
![](https://oscdn.geek-share.com/Uploads/Images/Content/201612/07/a40628815e9aeddc0115418252406c8a.png)
增加了一个Listener来解耦UserService和其他服务,即注册成功后,只需要通知相关的监听器,不需要关系它们如何处理。增删功能非常容易。
这就是一个典型的事件处理模型/观察者,解耦目标对象和它的依赖对象,目标只需要通知它的依赖对象,具体怎么处理,依赖对象自己决定。比如是异步还是同步,延迟还是非延迟等。
上边其实也使用了DIP(依赖倒置原则),依赖于抽象,而不是具体。
还是就是使用了IoC思想,即以前主动去创建它依赖的Service,现在只是被动等待别人注册进来。
其他的例子还有如GUI中的按钮和动作的关系,按钮和动作本身都是一种抽象,每个不同的按钮的动作可能不一样;如“文件-->新建”打开新建窗口;点击“关闭”按钮关闭窗口等等。
主要目的是:松散耦合对象间的一对多的依赖关系,如按钮和动作的关系;
如何实现呢?面向接口编程(即面向抽象编程),而非面向实现。即按钮和动作可以定义为接口,这样它俩的依赖是最小的(如在Java中,没有比接口更抽象的了)。
有朋友会问,我刚开始学的时候也是这样:抽象类不也行吗?记住一个原则:接口目的是抽象,抽象类目的是复用;所以如果接触过servlet/struts2/spring等框架,大家都应该知道:
Servlet<-----GenericServlet<-----HttpServlet<------我们自己的
Action<------ActionSupport<------我们自己的
DaoInterface<------××DaoSupport<-----我们自己的
从上边大家应该能体会出接口、抽象类的主要目的了。现在想想其实很简单。
在Java中接口还一个非常重要的好处:接口是可以多实现的,类/抽象类只能单继承,所以使用接口可以非常容易扩展新功能(还可以实现所谓的mixin),类/抽象类办不到。
扯远了,再来看看Java GUI世界里的事件驱动模型吧:
如果写过AWT/Swing程序,应该知道其所有组件都继承自java.awt.Component抽象类,其内部提供了addXXXListener(XXXListener l) 注册监听器的方法,即Component与实际动作之间依赖于XXXListener抽象。
比如获取焦点事件,很多组件都可以有这个事件,是我们知道组件获取到焦点后需要一个处理,虽然每个组件如何处理是特定的(具体的),但我们可以抽象一个FocusListener,让所有具体实现它然后提供具体动作,这样组件只需依赖于FocusListener抽象,而不是具体。
还有如java.awt.Button,提供了一个addActionListener(ActionListener l),用于注册点击后触发的ActionListener实现。
组件是一个抽象类,其好处主要是复用,比如复用这些监听器的触发及管理等。
JavaBean规范提供了JavaBean的PropertyEditorSupport及PropertyChangeListener支持。
PropertyEditorSupport就是目标,而PropertyChangeListener就是监听器,大家可以google搜索下,具体网上有很多例子。
JDK内部直接提供了观察者模式的抽象:
目标:java.util.Observable,提供了目标需要的关键抽象:addObserver/deleteObserver/
观察者:java.util.Observer,提供了观察者需要的主要抽象:update(Observable o,
接下来是我们的重点:spring提供的事件驱动模型。
首先看一下Spring提供的事件驱动模型体系图:
![](https://oscdn.geek-share.com/Uploads/Images/Content/201612/07/e56a4ae5c634cb2c1357a780aa9ee737.png)
具体代表者是:ApplicationEvent:
1、其继承自JDK的EventObject,JDK要求所有事件将继承它,并通过source得到事件源,比如我们的AWT事件体系也是继承自它;
2、系统默认提供了如下ApplicationEvent事件实现:
![](https://oscdn.geek-share.com/Uploads/Images/Content/201612/07/7d08fa479d4a0459d4f14f1feccab5ca.png)
只有一个ApplicationContextEvent,表示ApplicationContext容器事件,且其又有如下实现:
ContextStartedEvent:ApplicationContext启动后触发的事件;(目前版本没有任何作用)
ContextStoppedEvent:ApplicationContext停止后触发的事件;(目前版本没有任何作用)
ContextRefreshedEvent:ApplicationContext初始化或刷新完成后触发的事件;(容器初始化完成后调用)
ContextClosedEvent:ApplicationContext关闭后触发的事件;(如web容器关闭时自动会触发spring容器的关闭,如果是普通java应用,需要调用ctx.registerShutdownHook();注册虚拟机关闭时的钩子才行)
注:org.springframework.context.support.AbstractApplicationContext抽象类实现了LifeCycle的start和stop回调并发布ContextStartedEvent和ContextStoppedEvent事件;但是无任何实现调用它,所以目前无任何作用。
具体代表者是:ApplicationEventPublisher及ApplicationEventMulticaster,系统默认提供了如下实现:
![](https://oscdn.geek-share.com/Uploads/Images/Content/201604/924a10ad92fe322215a492f320014f8c.png)
1、ApplicationContext 接口继承了ApplicationEventPublisher,并在AbstractApplicationContext实现了具体代码,实际执行是委托给ApplicationEventMulticaster(可以认为是多播):
Java代码
[java]
view plain
copy
public void publishEvent(ApplicationEvent event) {
Assert.notNull(event, "Event must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Publishing event in " + getDisplayName() + ": " + event);
}
getApplicationEventMulticaster().multicastEvent(event);
if (this.parent != null) {
this.parent.publishEvent(event);
}
}
我们常用的ApplicationContext都继承自AbstractApplicationContext,如ClassPathXmlApplicationContext、XmlWebApplicationContext等。所以自动拥有这个功能。
2、ApplicationContext自动到本地容器里找一个名字为”“的ApplicationEventMulticaster实现,如果没有自己new一个SimpleApplicationEventMulticaster。
[java]
view plain
copy
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
if (logger.isDebugEnabled()) {
logger.debug("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
}
}
else {
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate ApplicationEventMulticaster with name '" +
APPLICATION_EVENT_MULTICASTER_BEAN_NAME +
"': using default [" + this.applicationEventMulticaster + "]");
}
}
}
其中SimpleApplicationEventMulticaster发布事件的代码如下:
Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/9e12f1d3e499fc949c886e7c9e0484f9)
public void multicastEvent(final ApplicationEvent event) {
for (final ApplicationListener listener : getApplicationListeners(event)) {
Executor executor = getTaskExecutor();
if (executor != null) {
executor.execute(new Runnable() {
public void run() {
listener.onApplicationEvent(event);
}
});
}
else {
listener.onApplicationEvent(event);
}
}
}
大家可以看到如果给它一个executor(java.util.concurrent.Executor),它就可以异步支持发布事件了。否则就是通过发送。
所以我们发送事件只需要通过ApplicationContext.publishEvent即可,没必要再创建自己的实现了。除非有必要。
具体代表者是:ApplicationListener
1、其继承自JDK的EventListener,JDK要求所有监听器将继承它,比如我们的AWT事件体系也是继承自它;
2、ApplicationListener接口:
Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/9e12f1d3e499fc949c886e7c9e0484f9)
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E event);
}
其只提供了onApplicationEvent方法,我们需要在该方法实现内部判断事件类型来处理,也没有提供按顺序触发监听器的语义,所以Spring提供了另一个接口,SmartApplicationListener:
Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/9e12f1d3e499fc949c886e7c9e0484f9)
public interface SmartApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {
//如果实现支持该事件类型 那么返回true
boolean supportsEventType(Class<? extends ApplicationEvent> eventType);
//如果实现支持“目标”类型,那么返回true
boolean supportsSourceType(Class<?> sourceType);
//顺序,即监听器执行的顺序,值越小优先级越高
int getOrder();
}
该接口可方便实现去判断支持的事件类型、目标类型,及执行顺序。
本例子模拟一个给多个人发送内容(类似于报纸新闻)的例子。
Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/9e12f1d3e499fc949c886e7c9e0484f9)
package com.zuidaima.hello;
import org.springframework.context.ApplicationEvent;
public class ContentEvent extends ApplicationEvent {
public ContentEvent(final String content) {
super(content);
}
}
非常简单,如果用户发送内容,只需要通过构造器传入内容,然后通过getSource即可获取。
之所以说无序,类似于AOP机制,顺序是无法确定的。
Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/9e12f1d3e499fc949c886e7c9e0484f9)
package com.zuidaima.hello;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class LisiListener implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(final ApplicationEvent event) {
if(event instanceof ContentEvent) {
System.out.println("李四收到了新的内容:" + event.getSource());
}
}
}
1、使用@Compoent注册Bean即可;
2、在实现中需要判断event类型是ContentEvent才可以处理;
更简单的办法是通过泛型指定类型,如下所示
Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/9e12f1d3e499fc949c886e7c9e0484f9)
package com.zuidaima.hello;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class ZhangsanListener implements ApplicationListener<ContentEvent> {
@Override
public void onApplicationEvent(final ContentEvent event) {
System.out.println("张三收到了新的内容:" + event.getSource());
}
}
实现SmartApplicationListener接口即可。
Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/9e12f1d3e499fc949c886e7c9e0484f9)
package com.zuidaima.hello;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class WangwuListener implements SmartApplicationListener {
@Override
public boolean supportsEventType(final Class<? extends ApplicationEvent> eventType) {
return eventType == ContentEvent.class;
}
@Override
public boolean supportsSourceType(final Class<?> sourceType) {
return sourceType == String.class;
}
@Override
public void onApplicationEvent(final ApplicationEvent event) {
System.out.println("王五在孙六之前收到新的内容:" + event.getSource());
}
@Override
public int getOrder() {
return 1;
}
}
Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/9e12f1d3e499fc949c886e7c9e0484f9)
package com.zuidaima.hello;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class SunliuListener implements SmartApplicationListener {
@Override
public boolean supportsEventType(final Class<? extends ApplicationEvent> eventType) {
return eventType == ContentEvent.class;
}
@Override
public boolean supportsSourceType(final Class<?> sourceType) {
return sourceType == String.class;
}
@Override
public void onApplicationEvent(final ApplicationEvent event) {
System.out.println("孙六在王五之后收到新的内容:" + event.getSource());
}
@Override
public int getOrder() {
return 2;
}
}
supportsEventType:用于指定支持的事件类型,只有支持的才调用onApplicationEvent;
supportsSourceType:支持的目标类型,只有支持的才调用onApplicationEvent;
getOrder:即顺序,越小优先级越高
4.1、配置文件
Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/9e12f1d3e499fc949c886e7c9e0484f9)
<context:component-scan base-package="com.zuidaima"/>
就一句话,自动扫描注解Bean。
4.2、测试类
Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/9e12f1d3e499fc949c886e7c9e0484f9)
package com.zuidaima;
import com.zuidaima.hello.ContentEvent;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:spring-config-hello.xml"})
public class HelloIT {
@Autowired
private ApplicationContext applicationContext;
@Test
public void testPublishEvent() {
applicationContext.publishEvent(new ContentEvent("今年是龙年的博客更新了"));
}
}
接着会输出:
Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/9e12f1d3e499fc949c886e7c9e0484f9)
王五在孙六之前收到新的内容:今年是龙年的博客更新了
孙六在王五之后收到新的内容:今年是龙年的博客更新了
李四收到了新的内容:今年是龙年的博客更新了
张三收到了新的内容:今年是龙年的博客更新了
一个简单的测试例子就演示完毕,而且我们使用spring的事件机制去写相关代码会非常简单。
具体请下载源代码参考com.zuidaima.register包里的代码。此处贴一下源码结构:
![](https://oscdn.geek-share.com/Uploads/Images/Content/201612/07/8b34ee099c6ef0417adcfc544490b036.png)
这里讲解一下Spring对异步事件机制的支持,实现方式有两种:
即只要是触发事件都是以异步执行,具体配置(spring-config-register.xml)如下:
Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/9e12f1d3e499fc949c886e7c9e0484f9)
<task:executor id="executor" pool-size="10" />
<!-- 名字必须是applicationEventMulticaster和messageSource是一样的,默认找这个名字的对象 -->
<!-- 名字必须是applicationEventMulticaster,因为AbstractApplicationContext默认找个 -->
<!-- 如果找不到就new一个,但不是异步调用而是同步调用 -->
<bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
<!-- 注入任务执行器 这样就实现了异步调用(缺点是全局的,要么全部异步,要么全部同步(删除这个属性即是同步)) -->
<property name="taskExecutor" ref="executor"/>
</bean>
通过注入taskExecutor来完成异步调用。具体实现可参考之前的代码介绍。这种方式的缺点很明显:要么大家都是异步,要么大家都不是。所以不推荐使用这种方式。
spring3提供了@Aync注解来完成异步调用。此时我们可以使用这个新特性来完成异步调用。不仅支持异步调用,还支持简单的任务调度,比如我的项目就去掉Quartz依赖,直接使用spring3这个新特性,具体可参考spring-config.xml。
2.1、开启异步调用支持
Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/9e12f1d3e499fc949c886e7c9e0484f9)
<!-- 开启@AspectJ AOP代理 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!-- 任务调度器 -->
<task:scheduler id="scheduler" pool-size="10"/>
<!-- 任务执行器 -->
<task:executor id="executor" pool-size="10"/>
<!--开启注解调度支持 @Async @Scheduled-->
<task:annotation-driven executor="executor" scheduler="scheduler" proxy-target-class="true"/>
2.2、配置监听器让其支持异步调用
Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/9e12f1d3e499fc949c886e7c9e0484f9)
@Component
public class EmailRegisterListener implements ApplicationListener<RegisterEvent> {
@Async
@Override
public void onApplicationEvent(final RegisterEvent event) {
System.out.println("注册成功,发送确认邮件给:" + ((User)event.getSource()).getUsername());
}
}
使用@Async注解即可,非常简单。
这样不仅可以支持通过调用,也支持异步调用,非常的灵活,实际应用推荐大家使用这种方式。
通过如上,大体了解了Spring的事件机制,可以使用该机制非常简单的完成如注册流程,而且对于比较耗时的调用,可以直接使用Spring自身的异步支持来优化。
事件驱动模型也就是我们常说的观察者,或者发布-订阅模型;理解它的几个关键点:
首先是一种对象间的一对多的关系;最简单的如交通信号灯,信号灯是目标(一方),行人注视着信号灯(多方);
当目标发送改变(发布),观察者(订阅者)就可以接收到改变;
观察者如何处理(如行人如何走,是快走/慢走/不走,目标不会管的),目标无需干涉;所以就松散耦合了它们之间的关系。
接下来先看一个用户注册的例子:
![](https://oscdn.geek-share.com/Uploads/Images/Content/201612/07/4bc4cf7dc0008b267eb3412ec453a062.png)
用户注册成功后,需要做这么多事:
1、加积分
2、发确认邮件
3、如果是游戏帐户,可能赠送游戏大礼包
4、索引用户数据
…………
问题:
UserService和其他Service耦合严重,增删功能比较麻烦;
有些功能可能需要调用第三方系统,如增加积分/索引用户,速度可能比较慢,此时需要异步支持;这个如果使用Spring,可以轻松解决,后边再介绍;
从如上例子可以看出,应该使用一个观察者来解耦这些Service之间的依赖关系,如图:
![](https://oscdn.geek-share.com/Uploads/Images/Content/201612/07/a40628815e9aeddc0115418252406c8a.png)
增加了一个Listener来解耦UserService和其他服务,即注册成功后,只需要通知相关的监听器,不需要关系它们如何处理。增删功能非常容易。
这就是一个典型的事件处理模型/观察者,解耦目标对象和它的依赖对象,目标只需要通知它的依赖对象,具体怎么处理,依赖对象自己决定。比如是异步还是同步,延迟还是非延迟等。
上边其实也使用了DIP(依赖倒置原则),依赖于抽象,而不是具体。
还是就是使用了IoC思想,即以前主动去创建它依赖的Service,现在只是被动等待别人注册进来。
其他的例子还有如GUI中的按钮和动作的关系,按钮和动作本身都是一种抽象,每个不同的按钮的动作可能不一样;如“文件-->新建”打开新建窗口;点击“关闭”按钮关闭窗口等等。
主要目的是:松散耦合对象间的一对多的依赖关系,如按钮和动作的关系;
如何实现呢?面向接口编程(即面向抽象编程),而非面向实现。即按钮和动作可以定义为接口,这样它俩的依赖是最小的(如在Java中,没有比接口更抽象的了)。
有朋友会问,我刚开始学的时候也是这样:抽象类不也行吗?记住一个原则:接口目的是抽象,抽象类目的是复用;所以如果接触过servlet/struts2/spring等框架,大家都应该知道:
Servlet<-----GenericServlet<-----HttpServlet<------我们自己的
Action<------ActionSupport<------我们自己的
DaoInterface<------××DaoSupport<-----我们自己的
从上边大家应该能体会出接口、抽象类的主要目的了。现在想想其实很简单。
在Java中接口还一个非常重要的好处:接口是可以多实现的,类/抽象类只能单继承,所以使用接口可以非常容易扩展新功能(还可以实现所谓的mixin),类/抽象类办不到。
Java GUI事件驱动模型/观察者
扯远了,再来看看Java GUI世界里的事件驱动模型吧:如果写过AWT/Swing程序,应该知道其所有组件都继承自java.awt.Component抽象类,其内部提供了addXXXListener(XXXListener l) 注册监听器的方法,即Component与实际动作之间依赖于XXXListener抽象。
比如获取焦点事件,很多组件都可以有这个事件,是我们知道组件获取到焦点后需要一个处理,虽然每个组件如何处理是特定的(具体的),但我们可以抽象一个FocusListener,让所有具体实现它然后提供具体动作,这样组件只需依赖于FocusListener抽象,而不是具体。
还有如java.awt.Button,提供了一个addActionListener(ActionListener l),用于注册点击后触发的ActionListener实现。
组件是一个抽象类,其好处主要是复用,比如复用这些监听器的触发及管理等。
JavaBean规范的事件驱动模型/观察者
JavaBean规范提供了JavaBean的PropertyEditorSupport及PropertyChangeListener支持。PropertyEditorSupport就是目标,而PropertyChangeListener就是监听器,大家可以google搜索下,具体网上有很多例子。
Java提供的事件驱动模型/观察者抽象
JDK内部直接提供了观察者模式的抽象:目标:java.util.Observable,提供了目标需要的关键抽象:addObserver/deleteObserver/
notifyObservers()等,具体请参考javadoc。
观察者:java.util.Observer,提供了观察者需要的主要抽象:update(Observable o,
Object arg),此处还提供了一种推模型(目标主动把数据通过arg推到观察者)/拉模型(目标需要根据o自己去拉数据,arg为null)。
因为网上介绍的非常多了,请google搜索了解如何使用这个抽象及推/拉模型的优缺点。
接下来是我们的重点:spring提供的事件驱动模型。
Spring提供的事件驱动模型/观察者抽象
首先看一下Spring提供的事件驱动模型体系图: ![](https://oscdn.geek-share.com/Uploads/Images/Content/201612/07/e56a4ae5c634cb2c1357a780aa9ee737.png)
事件
具体代表者是:ApplicationEvent:1、其继承自JDK的EventObject,JDK要求所有事件将继承它,并通过source得到事件源,比如我们的AWT事件体系也是继承自它;
2、系统默认提供了如下ApplicationEvent事件实现:
![](https://oscdn.geek-share.com/Uploads/Images/Content/201612/07/7d08fa479d4a0459d4f14f1feccab5ca.png)
只有一个ApplicationContextEvent,表示ApplicationContext容器事件,且其又有如下实现:
ContextStartedEvent:ApplicationContext启动后触发的事件;(目前版本没有任何作用)
ContextStoppedEvent:ApplicationContext停止后触发的事件;(目前版本没有任何作用)
ContextRefreshedEvent:ApplicationContext初始化或刷新完成后触发的事件;(容器初始化完成后调用)
ContextClosedEvent:ApplicationContext关闭后触发的事件;(如web容器关闭时自动会触发spring容器的关闭,如果是普通java应用,需要调用ctx.registerShutdownHook();注册虚拟机关闭时的钩子才行)
注:org.springframework.context.support.AbstractApplicationContext抽象类实现了LifeCycle的start和stop回调并发布ContextStartedEvent和ContextStoppedEvent事件;但是无任何实现调用它,所以目前无任何作用。
目标(发布事件者)
具体代表者是:ApplicationEventPublisher及ApplicationEventMulticaster,系统默认提供了如下实现:![](https://oscdn.geek-share.com/Uploads/Images/Content/201604/924a10ad92fe322215a492f320014f8c.png)
1、ApplicationContext 接口继承了ApplicationEventPublisher,并在AbstractApplicationContext实现了具体代码,实际执行是委托给ApplicationEventMulticaster(可以认为是多播):
Java代码
[java]
view plain
copy
public void publishEvent(ApplicationEvent event) {
Assert.notNull(event, "Event must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Publishing event in " + getDisplayName() + ": " + event);
}
getApplicationEventMulticaster().multicastEvent(event);
if (this.parent != null) {
this.parent.publishEvent(event);
}
}
我们常用的ApplicationContext都继承自AbstractApplicationContext,如ClassPathXmlApplicationContext、XmlWebApplicationContext等。所以自动拥有这个功能。
2、ApplicationContext自动到本地容器里找一个名字为”“的ApplicationEventMulticaster实现,如果没有自己new一个SimpleApplicationEventMulticaster。
[java]
view plain
copy
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
if (logger.isDebugEnabled()) {
logger.debug("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
}
}
else {
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate ApplicationEventMulticaster with name '" +
APPLICATION_EVENT_MULTICASTER_BEAN_NAME +
"': using default [" + this.applicationEventMulticaster + "]");
}
}
}
其中SimpleApplicationEventMulticaster发布事件的代码如下:
Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
public void multicastEvent(final ApplicationEvent event) {
for (final ApplicationListener listener : getApplicationListeners(event)) {
Executor executor = getTaskExecutor();
if (executor != null) {
executor.execute(new Runnable() {
public void run() {
listener.onApplicationEvent(event);
}
});
}
else {
listener.onApplicationEvent(event);
}
}
}
大家可以看到如果给它一个executor(java.util.concurrent.Executor),它就可以异步支持发布事件了。否则就是通过发送。
所以我们发送事件只需要通过ApplicationContext.publishEvent即可,没必要再创建自己的实现了。除非有必要。
监听器
具体代表者是:ApplicationListener1、其继承自JDK的EventListener,JDK要求所有监听器将继承它,比如我们的AWT事件体系也是继承自它;
2、ApplicationListener接口:
Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E event);
}
其只提供了onApplicationEvent方法,我们需要在该方法实现内部判断事件类型来处理,也没有提供按顺序触发监听器的语义,所以Spring提供了另一个接口,SmartApplicationListener:
Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
public interface SmartApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {
//如果实现支持该事件类型 那么返回true
boolean supportsEventType(Class<? extends ApplicationEvent> eventType);
//如果实现支持“目标”类型,那么返回true
boolean supportsSourceType(Class<?> sourceType);
//顺序,即监听器执行的顺序,值越小优先级越高
int getOrder();
}
该接口可方便实现去判断支持的事件类型、目标类型,及执行顺序。
Spring事件机制的简单例子
本例子模拟一个给多个人发送内容(类似于报纸新闻)的例子。
1、定义事件
Java代码[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
package com.zuidaima.hello;
import org.springframework.context.ApplicationEvent;
public class ContentEvent extends ApplicationEvent {
public ContentEvent(final String content) {
super(content);
}
}
非常简单,如果用户发送内容,只需要通过构造器传入内容,然后通过getSource即可获取。
2、定义无序监听器
之所以说无序,类似于AOP机制,顺序是无法确定的。Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
package com.zuidaima.hello;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class LisiListener implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(final ApplicationEvent event) {
if(event instanceof ContentEvent) {
System.out.println("李四收到了新的内容:" + event.getSource());
}
}
}
1、使用@Compoent注册Bean即可;
2、在实现中需要判断event类型是ContentEvent才可以处理;
更简单的办法是通过泛型指定类型,如下所示
Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
package com.zuidaima.hello;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class ZhangsanListener implements ApplicationListener<ContentEvent> {
@Override
public void onApplicationEvent(final ContentEvent event) {
System.out.println("张三收到了新的内容:" + event.getSource());
}
}
3、定义有序监听器
实现SmartApplicationListener接口即可。Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
package com.zuidaima.hello;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class WangwuListener implements SmartApplicationListener {
@Override
public boolean supportsEventType(final Class<? extends ApplicationEvent> eventType) {
return eventType == ContentEvent.class;
}
@Override
public boolean supportsSourceType(final Class<?> sourceType) {
return sourceType == String.class;
}
@Override
public void onApplicationEvent(final ApplicationEvent event) {
System.out.println("王五在孙六之前收到新的内容:" + event.getSource());
}
@Override
public int getOrder() {
return 1;
}
}
Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
package com.zuidaima.hello;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class SunliuListener implements SmartApplicationListener {
@Override
public boolean supportsEventType(final Class<? extends ApplicationEvent> eventType) {
return eventType == ContentEvent.class;
}
@Override
public boolean supportsSourceType(final Class<?> sourceType) {
return sourceType == String.class;
}
@Override
public void onApplicationEvent(final ApplicationEvent event) {
System.out.println("孙六在王五之后收到新的内容:" + event.getSource());
}
@Override
public int getOrder() {
return 2;
}
}
supportsEventType:用于指定支持的事件类型,只有支持的才调用onApplicationEvent;
supportsSourceType:支持的目标类型,只有支持的才调用onApplicationEvent;
getOrder:即顺序,越小优先级越高
4、测试
4.1、配置文件Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
<context:component-scan base-package="com.zuidaima"/>
就一句话,自动扫描注解Bean。
4.2、测试类
Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
package com.zuidaima;
import com.zuidaima.hello.ContentEvent;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:spring-config-hello.xml"})
public class HelloIT {
@Autowired
private ApplicationContext applicationContext;
@Test
public void testPublishEvent() {
applicationContext.publishEvent(new ContentEvent("今年是龙年的博客更新了"));
}
}
接着会输出:
Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
王五在孙六之前收到新的内容:今年是龙年的博客更新了
孙六在王五之后收到新的内容:今年是龙年的博客更新了
李四收到了新的内容:今年是龙年的博客更新了
张三收到了新的内容:今年是龙年的博客更新了
一个简单的测试例子就演示完毕,而且我们使用spring的事件机制去写相关代码会非常简单。
Spring事件机制实现之前提到的注册流程
具体请下载源代码参考com.zuidaima.register包里的代码。此处贴一下源码结构:![](https://oscdn.geek-share.com/Uploads/Images/Content/201612/07/8b34ee099c6ef0417adcfc544490b036.png)
这里讲解一下Spring对异步事件机制的支持,实现方式有两种:
1、全局异步
即只要是触发事件都是以异步执行,具体配置(spring-config-register.xml)如下:Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
<task:executor id="executor" pool-size="10" />
<!-- 名字必须是applicationEventMulticaster和messageSource是一样的,默认找这个名字的对象 -->
<!-- 名字必须是applicationEventMulticaster,因为AbstractApplicationContext默认找个 -->
<!-- 如果找不到就new一个,但不是异步调用而是同步调用 -->
<bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
<!-- 注入任务执行器 这样就实现了异步调用(缺点是全局的,要么全部异步,要么全部同步(删除这个属性即是同步)) -->
<property name="taskExecutor" ref="executor"/>
</bean>
通过注入taskExecutor来完成异步调用。具体实现可参考之前的代码介绍。这种方式的缺点很明显:要么大家都是异步,要么大家都不是。所以不推荐使用这种方式。
2、更灵活的异步支持
spring3提供了@Aync注解来完成异步调用。此时我们可以使用这个新特性来完成异步调用。不仅支持异步调用,还支持简单的任务调度,比如我的项目就去掉Quartz依赖,直接使用spring3这个新特性,具体可参考spring-config.xml。2.1、开启异步调用支持
Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
<!-- 开启@AspectJ AOP代理 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!-- 任务调度器 -->
<task:scheduler id="scheduler" pool-size="10"/>
<!-- 任务执行器 -->
<task:executor id="executor" pool-size="10"/>
<!--开启注解调度支持 @Async @Scheduled-->
<task:annotation-driven executor="executor" scheduler="scheduler" proxy-target-class="true"/>
2.2、配置监听器让其支持异步调用
Java代码
[java] view
plain copy
![](https://oscdn.geek-share.com/Uploads/Images/Content/201611/a7c8e286f463007e2a900848b93dd72c.png)
@Component
public class EmailRegisterListener implements ApplicationListener<RegisterEvent> {
@Async
@Override
public void onApplicationEvent(final RegisterEvent event) {
System.out.println("注册成功,发送确认邮件给:" + ((User)event.getSource()).getUsername());
}
}
使用@Async注解即可,非常简单。
这样不仅可以支持通过调用,也支持异步调用,非常的灵活,实际应用推荐大家使用这种方式。
通过如上,大体了解了Spring的事件机制,可以使用该机制非常简单的完成如注册流程,而且对于比较耗时的调用,可以直接使用Spring自身的异步支持来优化。
相关文章推荐
- Spring基于事件驱动模型的订阅发布模式代码实例详解
- Spring基于事件驱动模型的订阅发布模式代码实例详解
- Spring基于事件驱动模型的订阅发布模式代码实例详解
- Spring基于事件驱动模型的订阅发布模式代码实例详解
- 基于.Net的发布订阅模式API的实现与详解(一)
- 基于JS原生的事件订阅和发布模式代码
- 基于spring-redis发布订阅模式的实现
- spring整合activemq发布订阅消息模式
- Spring Data Redis实现消息队列——发布/订阅模式
- Shuttle ESB(四)——发布订阅模式实例介绍(1)
- Sql Server2008 ServiceBroker发布/订阅模式实例
- Spring中的发布订阅模式
- 一个基于反射的消息发布订阅模型详解
- Spring结合java Quartz配置实例代码详解
- 详解MongoDB和Spring整合的实例代码
- js 发布订阅模式的实例讲解
- 分布式消息总线,基于.NET Socket Tcp的发布-订阅框架之离线支持,附代码下载
- angular中发布/订阅模式详解
- Spring Redis与ActiveMQ发布订阅模式源码分析
- JavaScript中发布/订阅模式的简单实例