文档章节

Spring核心——全局事件管理

随风溜达的向日葵
 随风溜达的向日葵
发布于 08/08 14:52
字数 1883
阅读 500
收藏 17

ApplicationContext是一个Context策略(见上下文与IoC),他除了提供最基础的IoC容器功能,还提供了MessageSource实现的国际化、全局事件、资源层级管理等等功能。本文将详细介绍Spring核心模块的事件管理机制。

Spring核心模块的事件机制和常规意义上的“事件”并没有太大区别(例如浏览器上的用户操作事件)都是通过订阅/发布模式实现的。

Spring事件管理的内容包括标准事件、自定义事件、注解标记处理器、异步事件处理、通用实体包装。下面将通过几个例子来说明这些内容,可执行代码请到本人的gitee库下载,本文的内容在包chkui.springcore.example.javabase.event中。

我们都知道在订阅/发布模式中至少要涉及三个部分——发布者(publisher)、订阅者(listener/subscriber)和事件(event)。针对这个模型Spring也提供了对应的两个接口——ApplicationEventPublisher、ApplicationListener以及一个抽象类ApplicationEvent。基本上,要使用Spring事件的功能,只要实现/继承这这三个接口/抽象类并按照Spring定好的规则来使用即可。掌握这个原则那么接下来的内容就好理解了。

标准事件

Spring为一些比较常规的事件制定了标准的事件类型和固定的发布方法,我们只需要定制好订阅者(listener/subscriber)就可以监听这些事件。

先指定2个订阅者:

package chkui.springcore.example.javabase.event.standard;
public class ContextStartedListener implements ApplicationListener<ContextStartedEvent> {
	@Override
	public void onApplicationEvent(ContextStartedEvent event) {
		System.out.println("Start Listener: I am start");
	}
}
package chkui.springcore.example.javabase.event.standard;
public class ContextStopListener implements ApplicationListener<ContextStoppedEvent> {
	@Override
	public void onApplicationEvent(ContextStoppedEvent event) {
		System.out.println("Stop Listener: I am stop");
	}
}

 然后运行使用他们:

package chkui.springcore.example.javabase.event;
@Configuration
public class EventApp {

	@Bean
	ContextStopListener contextStopListener() {
		return new ContextStopListener();
	}
	
	@Bean
	ContextStartedListener contextStartedListener() {
		return new ContextStartedListener();
	}
	
	public static void main(String[] args) {
		ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(EventApp.class);
		//发布start事件
		context.start();
		//发布stop事件
		context.stop();
        //关闭容器
		context.close();
	}
}

在例子代码中,ContextStartedListenerContextStopListener类都实现了ApplicationListener接口,然后通过onApplicationEvent的方法参数来指定监听的事件类型。在ConfigurableApplicationContext接口中已经为“start”和“stop”事件提供对应的发布方法。除了StartedEventStoppedEventSpring还为其他几项操作提供了标准事件:

  1. ContextRefreshedEvent:ConfigurableApplicationContext::refresh方法被调用后触发。事件发出的时机是所有的后置处理器已经执行、所有的Bean已经被加载、所有的ApplicationContext接口方法都可以提供服务。
  2. ContextStartedEvent:ConfigurableApplicationContext::start方法被调用后触发。
  3. ContextStoppedEvent:ConfigurableApplicationContext::stop方法被调用后触发。
  4. ContextClosedEvent:ConfigurableApplicationContext::close方法被调用后触发。
  5. RequestHandledEvent:这是一个用于Web容器的事件(例如启用了DispatcherServlet),当接收到前端请求时触发。

自定义事件

除了使用标准事件,我们还可以定义各种各样的事件。实现前面提到的三个接口/抽象类即可。

继承ApplicationEvent实现自定义事件:

package chkui.springcore.example.javabase.event.custom;
public class MyEvent extends ApplicationEvent {

	private String value = "This is my event!";
	
	public MyEvent(Object source,String value) {
		super(source);
		this.value = value;
	}

	public String getValue() {
		return value;
	}
}

定义事件对应的Listener:

package chkui.springcore.example.javabase.event.custom;
public class MyEventListener implements ApplicationListener<MyEvent> {
	public void onApplicationEvent(MyEvent event) {
		System.out.println("MyEventListener :" + event.getValue());
	}
}

然后通过ApplicationEventPublisher接口发布事件:

package chkui.springcore.example.javabase.event.custom;
@Service
public class MyEventService implements ApplicationEventPublisherAware {
	private ApplicationEventPublisher publisher;
	@Override
	public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
		publisher = applicationEventPublisher;
	}

	public void publish(String value) {
		publisher.publishEvent(new MyEvent(this, value));
	}
}

使用@EventListener实现订阅者

Spring Framework4.2之后可以直接使用@EventListener注解来指定事件的处理器,我们将上面的MyEventListener类进行简单的修改:

package chkui.springcore.example.javabase.event.custom;
public class MyEventListenerAnnotation{
	@EventListener
    public void handleMyEvent(MyEvent event) {
		System.out.println("MyEventListenerAnnotation :" + event.getValue());
    }
}

使用@EventListener可以不必实现ApplicationListener,只要添加为一个Bean即可。Spring会根据方法的参数类型订阅对应的事件。

我们也可以使用注解指定绑定的事件:

package chkui.springcore.example.javabase.event.custom;
public class MyEventListenerAnnotation{
	@EventListener(ContextStartedEvent.class})
    public void handleMyEvent() {
        //----
    }
}

 还可以指定一次性监听多个事件:

package chkui.springcore.example.javabase.event.standard;
public class MultiEventListener {
	@EventListener({ContextStartedEvent.class, ContextStoppedEvent.class})
    @Order(2)
	void contenxtStandadrEventHandle(ApplicationContextEvent event) {
		System.out.println("MultiEventListener:" + event.getClass().getSimpleName());
	}
}

注意上面代码中的@Order注解,同一个事件可以被多个订阅者订阅。在多个定于者存在的情况下可以使用@Order注解来指定他们的执行顺序,数值越小越优先执行。

EL表达式设定事件监听的条件

通过注解还可以使用SpringEL表达式来更细粒度的控制监听的范围,比如下面的例子仅仅当事件的实例中MyEvent.value == "Second publish!"才触发处理器:

事件:

package chkui.springcore.example.javabase.event.custom;
public class MyEvent extends ApplicationEvent {
	private String value = "This is my event!";
	public MyEvent(Object source,String value) {
		super(source);
		this.value = value;
	}

	public String getValue() {
		return value;
	}
}

通过EL表达式指定监听的数据:

package chkui.springcore.example.javabase.event.custom;
public class MyEventListenerElSp {
	@EventListener(condition="#p0.value == 'Second publish!'")
    public void handleMyEvent(MyEvent event) {
		System.out.println("MyEventListenerElSp :" + event.getValue());
    }
}

这样,当这个事件被发布,而且其中的成员变量value值等于"Second publish!",对应的MyEventListenerElSp::handleMyEvent方法才会被触发。EL表达式还可以使用通配符等等丰富的表现形式来设定过滤规则,后续介绍EL表达式时会详细说明。

通用包装事件

Spring还提供一个方式使用事件来包装实体类,起到传递数据但是不用重复定义多个事件的作用。看下面的例子。

我们先定义2个实体类:

package chkui.springcore.example.javabase.event.generics;
class PES {
	public String toString() {
		return "PRO EVOLUTION SOCCER";
	}
}
class WOW {
	public String toString() {
		return "World Of Warcraft";
	}
}

定义可以用于包装任何实体的事件,需要实现ResolvableTypeProvider接口:

package chkui.springcore.example.javabase.event.generics;
public class EntityWrapperEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {

	public EntityWrapperEvent(T entity) {
		super(entity);
	}

	public ResolvableType getResolvableType() {
		return ResolvableType.forClassWithGenerics(getClass(),
                ResolvableType.forInstance(getSource()));
	}

}

订阅者可以根据被包裹的entity的不同来监听不同的事件:

package chkui.springcore.example.javabase.event.generics;
public class EntiryWrapperEventListener {
	@EventListener
	public void handlePES(EntityWrapperEvent<PES> evnet) {
		System.out.println("EntiryWrapper PES: " +  evnet);
	}
	@EventListener
	public void handleWOW(EntityWrapperEvent<WOW> evnet) {
		System.out.println("EntiryWrapper WOW: " +  evnet);
	}
}

上面的代码起到最用的主要是ResolvableType.forInstance(getSource())这一行代码,getSource()方法来自于EventObject类,它实际上就是返回构造方法中super(entity)设定的entity实例。

写在最后的

订阅/发布模式是几乎所有软件程序都会触及的问题,无论是浏览器前端、还是古老的winMFC程序。而在后端应用中,对于使用过MQ工具或者Vertx这种纯事件轮询驱动的框架码友,应该已经请清楚这种订阅/发布+事件驱动的价值。它除了能够降低各层的耦合度,还能更有效的利用多线程而大大的提执行效率(当然对开发人员的要求也会高不少)。

对于Spring核心框架来说,事件的订阅/发布只是IoC容器的一个附属功能,Spring的核心价值并不在这个地方。Spring的订阅发布功能在实现层面至少现在并没有使用EventLoop的方式,还是类与类之间的直接调用,所以在性能上是完全无法向Vertx看齐的。不过Spring事件的机制还是能够起到事件驱动的效果,可以用来全局控制一些状态。如果选用Spring生态中的框架(boot等)作为我们的底层框架,现阶段还是应该使用IoC的方式来组合功能,而事件的订阅/发布仅仅用于辅助。

© 著作权归作者所有

共有 人打赏支持
随风溜达的向日葵
粉丝 275
博文 75
码字总数 161963
作品 0
广州
其他
加载中

评论(1)

vid
vid
学习了
【第1章 Spring概述与结构】1.2 Spring模块与结构

上一节:【第1章 Spring概述与结构】1.1 Spring起源与概述 基于Java Beans的配置管理,采用IOC的原理,特别是对依赖注入技术的使用。这些都用来减少各组件间对实施细则的相互依赖性。 一个核...

陶邦仁
2015/05/21
0
0
Spring核心——上下文与IoC

前面3篇分别介绍了IoC容器与Bean的关系、Bean与Bean之间的关系以及Bean自身的控制和管理。在了解Spinrg核心模式时,一定要谨记他的基本工作元素就是IoC容器和Bean,所有的功能是围绕着这2者展...

随风溜达的向日葵
07/02
0
0
JAVA关于Spring 面试题汇总

1 Spring 框架有哪些主要模块? 截止到目前Spring 框架已集成了 20 多个模块 。 这些模块主要被分如下图所示的核心容器 、 数据访问 / 集成 、Web、AOP (面向切面编程) 、 工具 、 消息和测...

Java高级架构
08/29
0
0
Spring面试基本问题(1)

1、什么是Spring框架?Spring框架有哪些主要模块? Spring框架是一个为Java应用程序的开发提供了综合、广泛的基础性支持的Java平台。Spring帮助开发者解决了开发中基础性的问题,使得开发人员...

yaohong
2016/11/08
0
0
Spring实战读书笔记(1)

Spring的根本使命是? 简化Java开发 为了降低Java开发的复杂性,Spring采取了哪4种关键策略? 1、基于POJO的轻量级和最小侵入性编程 2、通过依赖注入和面向接口实现松耦合 3、基于切面和惯例...

祥林会跟你远走高飞
2014/12/30
0
0

没有更多内容

加载失败,请刷新页面

加载更多

webSocket前台实现

webSocket前台实现 简单实现: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script type="application/javascript" src="js/base64.js"></script......

Airship
8分钟前
0
0
从零到一,使用实时音视频 SDK 一起开发一款 Zoom 吧

zoom(zoom.us) 是一款受到广泛使用的在线会议软件。相信各位一定在办公、会议、聊天等各种场景下体验或者使用过,作为一款成熟的商业软件,zoom 提供了稳定的实时音视频通话质量,以及白板、...

七牛云
9分钟前
0
0
Linux学习-10月16

9.1 正则介绍_grep 9.2 grep中 9.3 grep下 一、什么是正则 正则就是一串有规律的字符串,包括各种特殊符号 掌握正则对于编写shell有很大帮助 各种编程中都有正则,原理是一样的 二、grep简介...

wxy丶
16分钟前
0
0
设计模式学习与应用——单例模式

单例模式 作用:一个类只有一个实例,并且提供访问该实例的全局访问点 创建方式 1.懒汉方式 public class Singleton{//使外部无法访问这个变量,而要使用公共方法来获取private static ...

隔壁老余在这
25分钟前
0
0
亿级爆款背后,网易云音乐的生长之道

两年时间,破亿;四年时间,破4亿…… 据国内知名移动大数据监测平台Trustdata发布的《2017年下半年中国移动互联网发展分析报告》显示,2017年12月,网易云音乐MAU同比增长达43.1%,是移动音...

安卓绿色联盟
28分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部