前言
在使用spring的过程中,一般很少直接使用BeanFactory示例作为容器,更多的是使用ApplicationContext示例作为容器。
ApplicationContext容器提供观察者模式的时间机制
- ApplicationEvent:事件
- ApplicationListener:时间监听
- ApplicationContext:容器
应用程序只需要创建一个event并发布到容器中,Listener就会监听到此事件并进行处理。
此篇文章我们就来详细讨论一下Spring事件的各种用法。
自定义事件
借助spring提供的事件模型,我们可以很方便的定义、发布和监听自定义事件。
事件对象
自定义的事件对象需要实现ApplicationEvent接口(spring4.2版本后可以不实现,将在下面介绍)
比如我们定义一个MyEvent事件对象,其中text属性模拟事件属性1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24/**
* 自定义事件
*/
public class MyEvent extends ApplicationEvent {
private String text; //事件内容
public MyEvent(Object source) {
super(source);
}
public MyEvent(Object source, String text) {
super(source);
this.text = text;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
发布事件
我们只要通过ApplicationContext的publishEvent方法就可以发布一个自定义消息。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17/**
* 容器
*/
public class Application {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(Application.class);
//新建事件
MyEvent myEvent = new MyEvent("myEvent", "我是自定义事件的内容");
//向容器发布时间
ctx.publishEvent(myEvent);
}
}
事件监听
我们可以通过实现ApplicationListener接口来创建监听1
2
3
4
5
6
7
8
9
10
11/**
* 自定义事件监听
*/
public class MyEventListener implements ApplicationListener<MyEvent> {
public void onApplicationEvent(MyEvent event) {
System.out.println("监听到自定义事件:" + event.getText());
}
}
当然也可以使用更古老的方式监听,这种写法逻辑不清晰,个人不推荐1
2
3
4
5
6
7
8
9
10
11
12
13/**
* 事件监听
*/
public class MyEventListener2 implements ApplicationListener {
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof MyEvent) {
MyEvent myEvent = (MyEvent) event;
System.out.println("监听到自定义事件:" + myEvent.getText());
}
}
}
执行结果
运行Application的main函数后控制台输出1
监听到自定义事件:我是自定义事件的内容
spring内置的事件
Spring内置的ApplicationEvent有
事件 | 说明 |
---|---|
ContextClosedEvent | 当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。 |
ContextRefreshedEvent | 当ApplicationContext初始化或者刷新时触发该事件 |
ContextStartedEvent | 容器启动时发布 |
ContextStoppedEvent | 当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件 |
PayloadApplicationEvent | 在spring4.2中新引入的事件,大部分由spring内部使用;spring4.2之后向容器中发布的事件,不再强制要求继承自ApplicationEvent,当发布一个非ApplicationEvent对象时,spring会自动包装成PayloadApplicationEvent。 |
RequestHandledEvent | 这是一个 web-specific 事件,告诉所有 bean HTTP 请求已经被服务 |
事件监听
ContextRefreshedEvent事件监听器
1 | /** |
ContextStartedEvent事件监听器
1 | /** |
ContextClosedEvent事件监听器
1 | /** |
ContextStoppedEvent事件监听器
1 | /** |
发布事件
编写容器,其中ApplicationContext更强制设置为AnnotationConfigApplicationContext类型。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/**
* 容器
*/
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Application.class);
ctx.start();
MyEvent myEvent = new MyEvent("myEvent", "我是自定义事件的内容"); //新建事件
ctx.publishEvent(myEvent); //向容器发布时间
ctx.stop();
ctx.close();
}
}
执行结果
1 | 监听到ContextRefreshedEvent事件 |
PayloadApplicationEvent用法
Spring4.2版本优化了自定义事件类的写法,不再强制要求实现ApplicationEvent接口,定义一个普通的java类,然后通过容器发布。容器会自动将此对象包装成PayloadApplicationEvent事件,然后通过监听此事件获取原始对象。
事件对象
编写一个普通的java对象作为事件对象1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18/**
* 未实现ApplicationEvent的事件对象
*/
public class NormalEvent {
private String text;
public NormalEvent(String text) {
this.text = text;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
事件监听器
注意,此处监听了PayloadApplicationEvent事件,然后从此事件中获取被包装的原始对象1
2
3
4
5
6
7
8
9
10
11
12
13/**
* PayloadApplicationEvent事件监听
*/
public class NormalEventListener implements ApplicationListener<PayloadApplicationEvent> {
public void onApplicationEvent(PayloadApplicationEvent event) {
if (event.getPayload() instanceof NormalEvent) {
NormalEvent normalEvent = (NormalEvent) event.getPayload();
System.out.println("监听到自定义事件:" + normalEvent.getText());
}
}
}
@EventListener用法
Spring4.2还增加了@EventListener注解。
通过此注解监听器不再需要实现ApplicationListener接口,给普通的方法增加@EventListener注解,spring会自动注册一个ApplicationListener来匹配方法签名。
发布事件
可以看到,我们发布的是一个未实现ApplicationEvent的普通java对象1
2
3
4
5
6
7
8
9
10
11
12
13/**
* 容器
*/
public class Application {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(Application.class);
ctx.publishEvent(new NormalEvent("我是一个事件,但是并未实现ApplicationEvent接口"));
}
}
事件监听
1 | /** |
执行结果
1 | 监听到自定义事件:我是一个事件,但是并未实现ApplicationEvent接口 |
从上面可以看出来,从Spring4.2后,事件不再需要实现ApplicationEvent接口,监听器也不再需要实现ApplicationListener接口,通过简单自动包装事件对象和@EventListener注解,大大简化了事件的实现。
带条件的监听器
@EventListener注解,可以通过classes属性指定监听的消息类型(可以多个),还可以通过condition属性增加监听条件(SpELg表达式),例如下面1
2
3
4
5
6
7
8
9
10
11/**
* 使用EventListener注解的带条件的监听器
*/
public class NormalEventListener3 {
"#normalEvent.text==\"test\"") (classes = {NormalEvent.class}, condition =
public void handelNormalEvent(NormalEvent normalEvent) {
System.out.println("监听到自定义事件:" + normalEvent.getText());
}
}
1 | /** |
执行结果1
监听到自定义事件:test
从结果可以看出,只有第二个发布的事件被监听到了,通过这个功能,我们可以方便的监听符合条件的事件。
@EventListener注解方法返回非null
对于任何一个使用@EventListener注解的方法,允许定一个非void的返回类型。如果返回一个非null值,Spring将此结果作为一个新事件发布到容器。
容器
1 | /** |
监听器
1 | /** |
执行结果
1 | NormalEventListener4监听到自定义事件:我是一个事件,但是并未实现ApplicationEvent接口 |
@TransactionalEventListener注解
使用此注解,可以指定只有当外部事务完成后,再执行监听器。
注意点
由于Spring的事件处理是单线程的,如果一个事件被发布,在所有监听者处理完成前,该进程将被阻塞。
因此对于耗时的监听处理逻辑需要小心处理。如果碰到这样耗时的处理,可以考虑使用@Anyc注解来启动单独线程进行处理;对于@Anyc注解的用法,将来单独开文章解释。
参考文章:
https://projects.spring.io/spring-framework/
http://blog.csdn.net/xiejx618/article/details/44600369
http://blog.csdn.net/chenssy/article/details/8220089