背景
在开发工作中,会遇到一种场景,做完某一件事情以后,需要广播一些消息或者通知,告诉其他的模块进行一些事件处理,一般来说,可以一个一个发送请求去通知,但是有一种更好的方式,那就是事件监听,事件监听也是设计模式中 发布-订阅模式、观察者模式的一种实现。
观察者模式:简单的来讲就是你在做事情的时候身边有人在盯着你,当你做的某一件事情是旁边观察的人感兴趣的事情的时候,他会根据这个事情做一些其他的事,但是盯着你看的人必须要到你这里来登记,否则你无法通知到他(或者说他没有资格来盯着你做事情)。
正文
要想顺利的创建监听器,并起作用,这个过程中需要这样几个角色:
1、事件(event)可以封装和传递监听器中要处理的参数,如对象或字符串,并作为监听器中监听的目标。
2、监听器(listener)具体根据事件发生的业务处理模块,这里可以接收处理事件中封装的对象或字符串。
3、事件发布者(publisher)事件发生的触发者。
首先,介绍一下传统的非注解的监听器的实现方式,这样有利于了解一下注解实现的原理
第一个,我们需要定义一个事件(MyTestEvent ),需要继承spring的ApplicationEvent
package com.mu.event;
import org.springframework.context.ApplicationEvent;
public class MyTestEvent extends ApplicationEvent{
/**
*
*/
private static final long serialVersionUID = 1L;
private String msg ;
public MyTestEvent(Object source,String msg) {
super(source);
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
其次,需要定义一下监听器,自己定义的监听器需要实现ApplicationListener,同时泛型参数要加上自己要监听的事件Class名,在重写的方法onApplicationEvent中,添加自己的业务处理
package com.mu.listener;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import com.mu.event.MyTestEvent;
@Component
public class MyNoAnnotationListener implements ApplicationListener<MyTestEvent>{
@Override
public void onApplicationEvent(MyTestEvent event) {
System.out.println("非注解监听器:" + event.getMsg());
}
}
第三就是事件发布啦!有了事件,有了事件监听者,那么什么时候触发这个事件呢?每次想让监听器收到事件通知的时候,就可以调用一下事件发布的操作。注入一下spring的ApplicationContext,通过spring的应用上下文进行事件的发布,参数就是要发布事件的MyTestEvent事件对象
package com.mu.event;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
@Component
public class MyTestEventPubLisher {
@Autowired
private ApplicationContext applicationContext;
// 事件发布方法
public void pushListener(String msg) {
applicationContext.publishEvent(new MyTestEvent(this, msg));
}
}
接下来,测试一下非注解方式的事件发布和事件监听,写一个测试Controller,模拟调用一下事件发布操作
package com.mu.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.mu.event.MyTestEventPubLisher;
@Controller
public class TestEventListenerController {
@Autowired
private MyTestEventPubLisher publisher;
@RequestMapping(value = "/test/testPublishEvent1" )
public void testPublishEvent(){
publisher.pushListener("我来了!");
}
}
结果:
收到打印,说明已经在监听器中监听到了事件发布!
恭喜你,到目前为止,就已经了解了事件发布和监听的整个流程了。
进阶 spring的注解 实现事件监听
好处:不用每次都去实现ApplicationListener,可以在一个class中定义多个方法,用@EventListener来做方法级别的注解。例如:
package com.mu.listener;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import com.mu.event.MyTestEvent;
@Component
public class MyAnnotationListener {
@EventListener
public void listener1(MyTestEvent event) {
System.out.println("注解监听器1:" + event.getMsg());
}
}
此时,就可以有一个发布,两个监听器监听到发布的消息了,一个是注解方式,一个是非注解方式
结果:
我们可以发现,注解形式的监听器的执行走在了非注解的前面。
到此为止,想必你就了解到spring中的事件发布和监听器的注解和非注解的使用方式了。但是在实际工作中,事件监听经常会用在发送通知,消息、邮件等情况下,那么这个时候往往是需要异步执行的,不能在业务的主线程里面,那怎么样可以实现异步处理呢?当然你可以写一个线程,单独做这个事情,在此,我比较推荐的是用spring的@Async注解方式,一个简单的注解,就可以把某一个方法或者类下面的所有方法全部变成异步处理的方法,这样,就可以做到处理监听事件的时候也不会阻塞主进程了。
新增监听器listener2,在方法上加上@Async注解,但是此注解不能标注static修饰的方法
package com.mu.listener;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import com.mu.event.MyTestEvent;
@Component
public class MyAnnotationListener {
@EventListener
public void listener1(MyTestEvent event) {
System.out.println("注解监听器1:" + event.getMsg());
}
@EventListener
@Async
public void listener2(MyTestEvent event) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("注解监听器2:" + event.getMsg());
}
}
想要启动注解方式的异步处理办法,还需要做一下配置
注解的应用范围:
类:表示这个类中的所有方法都是异步的
方法:表示这个方法是异步的,如果类也注解了,则以这个方法的注解为准
配置:executor:指定一个缺省的executor给@Async使用。
例子:
配置参数:
- id:当配置多个executor时,被@Async(“id”)指定使用;也被作为线程名的前缀。
- pool-size:
- core size:最小的线程数,缺省:1
- max size:最大的线程数,缺省:Integer.MAX_VALUE
- queue-capacity:当最小的线程数已经被占用满后,新的任务会被放进queue里面,当这个queue的capacity也被占满之后,pool里面会创建新线程处理这个任务,直到总线程数达到了max size,这时系统会拒绝这个任务并抛出TaskRejectedException异常(缺省配置的情况下,可以通过rejection-policy来决定如何处理这种情况)。缺省值为:Integer.MAX_VALUE
- keep-alive:超过core size的那些线程,任务完成后,再经过这个时长(秒)会被结束掉
- rejection-policy:当pool已经达到max size的时候,如何处理新任务
- ABORT(缺省):抛出TaskRejectedException异常,然后不执行
- DISCARD:不执行,也不抛出异常
- DISCARD_OLDEST:丢弃queue中最旧的那个任务
- CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
配置例子:
1、如果是spring配置文件方式,可以在spring主配置中添加:
<task:annotation-driven executor="asyncExecutor" />
<task:executor id="asyncExecutor" pool-size="100-10000" queue-capacity="10"/>
2、如果是无xml的java配置方式的话,需要在配置类上添加@EnableAsync注解来开启异步注解。
实验结果:
本应该是
注解监听器1:我来了!
注解监听器2:我来了!
非注解监听器:我来了!
由于注解监听器2里面休眠了三秒钟再执行,但是并没有影响非注解监听器的执行,说明异步执行生效!
恭喜你,至此,你不仅仅了解了事件的发布和注解非注解方式的监听器实现,还了解了spring中异步线程的注解实现方式。