文档章节

Spring Event是什么鬼

八月下沙
 八月下沙
发布于 2016/03/31 18:46
字数 1520
阅读 227
收藏 12

一、什么是程序世界里的事件?

最常见的点击操作是一种事件,刷新操作是一种事件,类似的还有很多,在程序的世界里,事件的起源是输入设备的一个信号(比如中断响应),进而将事件(或者说信号及所带的信息)一步步地传递给需要响应的监听器(JS的事件机制就比较典型)。可以看出来,事件具有几个要素:起源、事件信息、可传递、可被响应。

起源:事实上大部分的应用里讲的事件,已经是被OS,被所在APP层层传递和包装后的事件

事件信息:事件传递到不同的应用层再往下继续传递的信息可能会有所不同,APP会根据自己的需要做相应的处理

可传递:比如:JS的事件冒泡

可被响应 :这是具体业务应该关心的点,触发了的事件该做什么事

理解了事件本质再来理解 spring event 就变得非常容易了,继续往下看

二、观察者模式

有时被称作发布/订阅模式,观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

当我们聚焦到软件系统内部时,同样可以找到很多“事件”的缩影,观察者模式为解决这类问题提供了一个方案。

三、Spring Event

Spring Event 同样遵循事件几个特性,有事件的起源,可被传递,可被响应等,使用Spring Event的好处是可以被Spring容器管理,可以使用容器资源。

起源:在 Spring Event 中,事件的起源可能是一段业务代码,当业务变更时,调用 Event publisher post 一个Event(事件信息)

//...需要触发事件的业务方法
ExampleEvent exampleEvent = new ExampleEvent();
//exampleEvent.set...
examplePublisher.publishEvent(event);

@Component("examplePublisher")
public class ExamplePublisherImpl implements ApplicationContextAware, ExamplePublisher {

    private ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }


    @Override
    public void publishEvent(ExampleEvent event) {
        this.context.publishEvent(event);
    }
}

那么,Spring Event 内部是如何将事件 Publish 出去的呢?

//AbstractApplicationContext.java
	/**
	 * Publish the given event to all listeners.
	 * <p>Note: Listeners get initialized after the MessageSource, to be able
	 * to access it within listener implementations. Thus, MessageSource
	 * implementations cannot publish events.
	 * @param event the event to publish (may be application-specific or a
	 * standard framework event)
	 */
	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);
		}
	}

	/**
	 * Return the internal ApplicationEventMulticaster used by the context.
	 * @return the internal ApplicationEventMulticaster (never {@code null})
	 * @throws IllegalStateException if the context has not been initialized yet
	 */
	private ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
		if (this.applicationEventMulticaster == null) {
			throw new IllegalStateException("ApplicationEventMulticaster not initialized - " +
					"call 'refresh' before multicasting events via the context: " + this);
		}
		return this.applicationEventMulticaster;
	}

事件通过 ApplicationContext 通知给相应的监听,实际上是调用 ApplicationEventMulticaster 接口实现的

//AbstractApplicationEventMulticaster.java
	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);
			}
		}
	}

突然想到一个问题,ApplicationContext并不是只处理一个事件,ApplicationEevent也并不是只有一种,不同的事件有自己的实现,那 multicasterEvent 时如何准确将事件 publish ?

/**
	 * Return a Collection of ApplicationListeners matching the given
	 * event type. Non-matching listeners get excluded early.
	 * @param event the event to be propagated. Allows for excluding
	 * non-matching listeners early, based on cached matching information.
	 * @return a Collection of ApplicationListeners
	 * @see org.springframework.context.ApplicationListener
	 */
	protected Collection<ApplicationListener> getApplicationListeners(ApplicationEvent event) {
		Class<? extends ApplicationEvent> eventType = event.getClass();
		Class sourceType = event.getSource().getClass();
		ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);  //这里定义了一个类用来做为缓存key,实际是重写了equal,比较的是eventType&sourceType
		ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
		if (retriever != null) {
			return retriever.getApplicationListeners();
		}
		else {
			retriever = new ListenerRetriever(true);
			LinkedList<ApplicationListener> allListeners = new LinkedList<ApplicationListener>();
			Set<ApplicationListener> listeners;
			Set<String> listenerBeans;
			synchronized (this.defaultRetriever) {  //此处参考正确使用Java事件通知
				listeners = new LinkedHashSet<ApplicationListener>(this.defaultRetriever.applicationListeners);
				listenerBeans = new LinkedHashSet<String>(this.defaultRetriever.applicationListenerBeans);
			}
			for (ApplicationListener listener : listeners) {
				if (supportsEvent(listener, eventType, sourceType)) { //通过反射判断Listener实例的参数是不是与提供的event一致
					retriever.applicationListeners.add(listener);
					allListeners.add(listener);
				}
			}
			if (!listenerBeans.isEmpty()) {
				BeanFactory beanFactory = getBeanFactory();
				for (String listenerBeanName : listenerBeans) {
					ApplicationListener listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class);
					if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
						retriever.applicationListenerBeans.add(listenerBeanName);
						allListeners.add(listener);
					}
				}
			}
			OrderComparator.sort(allListeners);
			this.retrieverCache.put(cacheKey, retriever);
			return allListeners;
		}
	}

至此,Spring Event 已经可以实现事件触发,前半部分已经结束了,并且我们已经知道 listener 中的方法是如何被调用了,但是这些 listener 是如何被添加到 Multicaster 的广播列表中的呢?

//AbstractApplicationContext.java

	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
		...
		// Initialize event multicaster for this context.
		initApplicationEventMulticaster();
		...
		// Check for listener beans and register them.
		registerListeners();		
		...
		}
	}
	
	/**
	 * Add beans that implement ApplicationListener as listeners.
	 * Doesn't affect other listeners, which can be added without being beans.
	 */
	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 lisName : listenerBeanNames) {
			getApplicationEventMulticaster().addApplicationListenerBean(lisName);
		}
	}

ApplicationContext 在启动时初始化各种 Bean,同时也会注册监听的事件(所有ApplicationListener接口的实现),这里讲的注册就是把监听类实例(只限那些特定的静态的监听实例)或者类名称放入 Multicaster 的监听队列中,只是放名称的目的是让ApplicationContext初始化完后再做注入。

与整个过程相关的还有两个对象,一个是 ApplicationListener 接口,所有Listener都要实现这个接口,否则 ApplicationContext 启动时无法注册监听,另一个是 ApplicationEvent -> EventObject,事件信息的传递需要 ApplicationEvent,事件信息需要继承 ApplicationEvent,另外ApplicationEvent本身继承自JDK的EventObject,其中 source 对象为同一个事件类,在不同的情况下,使用不同监听处理提供了可能。

另外,事件还可以设定为异步执行,只要往容器注入相应的Executor。

四、Guava EventBus

Event是事件的另一种优秀实现,相对来讲,比 Spring event 更容易上手,也更容易使用。EventBus的设计思想可参考:http://beneo.iteye.com/blog/1984072

五、相关文章

操作系统中断方式与轮询方式:http://lionwq.spaces.eepw.com.cn/articles/article/item/18936

javascript事件机制底层实现原理:http://www.cnblogs.com/yexiaochai/p/3477715.html

观察者模式:http://www.cnblogs.com/wangjq/archive/2012/07/12/2587966.html

正确使用Java事件通知:http://www.importnew.com/15446.html


© 著作权归作者所有

八月下沙
粉丝 5
博文 12
码字总数 10423
作品 0
杭州
部门经理
私信 提问
找找 Spring Event 源码中各种设计模式的使用

本文将按照Spring Event 是什么鬼的思路寻找 Spring 源码中与 Spring Event 有关的设计模式实现 初始化-工厂模式 AbstractApplicationContext.java/** * Initialize the ApplicationEventMu...

八月下沙
2016/04/10
83
0
厉害了,Spring Cloud for Alibaba 来了!

最近,Spring Cloud 发布了 Spring Cloud Alibaba 首个预览版本:Spring Cloud for Alibaba 0.2.0. 大家都好奇,这和阿里巴巴有什么关系?莫非是给阿里巴巴定制了一个 Spring Cloud ? 其实也...

Java技术栈
2018/11/22
0
0
Spring 3.0 Controller层单元测试

今天在对基于Spring mvc架构的项目写单元测试的时候,本来想用@RunWith的方式轻松搞定它。不曾想还不是那么so easy, 一方面是controller层没有联系起来,再者就是SpringJUnit4ClassRunner启动...

andy_zheng
2012/11/05
0
0
spring中的event listener模式和解耦

event,listener是observer模式一种体现,在spring 3.0.5中,已经可以使用annotation实现event和eventListner里。 我们以spring-webflow里的hotel booking为例,看一下实现,步骤如下: 1,建...

烀饼
2012/02/10
0
4
jpa怎么才能更加优雅的连表查询啊?

连表查,返回自定义值,动态sql,以前用mybatis的时候这些压根就不是问题,mapper文件全部帮你解决了,现在项目组非要搞个jpa,也没啥人真的会玩,网上看了一堆帖子,解决是解决了,代码多了...

落后君丶
2018/08/10
246
4

没有更多内容

加载失败,请刷新页面

加载更多

JWT学习总结

官方 https://jwt.io 英文原版 https://www.ietf.org/rfc/rfc7519.txt 或 https://tools.ietf.org/html/rfc7519 中文翻译 https://www.jianshu.com/p/10f5161dd9df 1. 概述 JSON Web Token(......

冷基
今天
4
0
AOP的学习(1)

AOP 理解AOP编程思想(面向方法、面向切面) spring AOP的概念 方面 -- 功能 目标 -- 原有方法 通知 -- 对原有方法增强的方法 连接点 -- 可以用来连接通知的地方(方法) 切入点 -- 将用来插入...

太猪-YJ
今天
4
0
一张图看懂亮度、明度、光度、光亮度、明亮度

亮度、明度、光亮度,Luminance和Brightness、lightness其实都是一个意思,只是起名字太难了。 提出一个颜色模型后,由于明度的取值与别人的不同,为了表示区别所以就另想一个词而已。 因此在...

linsk1998
昨天
11
0
Python应用:python链表示例

前言 python链表应用源码示例,需要用到python os模块方法、函数和类的应用。 首先,先简单的来了解下什么是链表?链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是...

python小白1
昨天
5
0
Source Insight加载源码

Source Insight是一个图形化的源代码查看工具(当然也可以作为编译工具)。如果一个项目的源代码较多,此工具可以很方便地查找到源代码自建的依赖关系。 1.创建工程 下图为Snort源代码的文件...

天王盖地虎626
昨天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部