今天咱们聊聊Spring中的事件,你也可以理解成消息,这是典型的观察者模式的实现,其主要目的是用于项目解耦。
Spring的事件默认是同步方式,不是所有的事件都是异步!!!
事件三要素:
-
发布者 -
订阅者(监听器) -
事件主体
1.事件监听任务
1.1 通过实现接口
//定义事件主体
@Data
public class CurrentUser extends ApplicationEvent {
private String name;
private int age;
public CurrentUser(Object source, String name, int age){
super(source);
this.name = name;
this.age = age;
}
}
//通过实现接口的方式监听事件
@Component
public class SubscriberClass implements ApplicationListener<CurrentUser> {
private static final Logger log = LoggerFactory.getLogger(SubscriberClass.class);
@Override
public void onApplicationEvent(CurrentUser currentUser) {
log.info("SubscriberClass + onApplicationEvent + " + currentUser.getName());
}
}
1.2通过@EventListener注解
@Component
public class SubscriberClass{
private static final Logger log = LoggerFactory.getLogger(SubscriberClass.class);
@EventListener
public void handleCurrentUserThird(CurrentUser currentUser){
log.info("SubscriberClass + handleCurrentUser + " + currentUser.getName());
}
}
1.3 监听器处理多个事件类型
如果你的项目需要一个监听器处理一种大的类型,你可以定义抽象类继承ApplicationEvent
, 然后将具体的事件类型继承该类型。
这时候你可以在监听器内处理该类别事件的逻辑, 例如:
//定义一个大的类型
public abstract class FirstType extends ApplicationEvent {
public FirstType(Object source) {
super(source);
}
}
//基于该类型的第一种事件类型
@Data
public static class DogEvent extends FirstType{
private String name;
public DogEvent(Object source, String name) {
super(source);
this.name = name;
}
}
//基于该类型的第二种事件类型
@Data
public static class WolfEvent extends FirstType{
private String name;
public WolfEvent(Object source, String name) {
super(source);
this.name = name;
}
}
//监听器处理 这一大类型的事件 收到事件后 根据具体的事件类型做出相应的逻辑处理
@EventListener
public void first(FirstType firstType) {
if (firstType instanceof CommonSpringEventType.DogEvent) {
CommonSpringEventType.DogEvent dogEvent = (CommonSpringEventType.DogEvent) firstType;
System.out.println("CommonSpringEventType.DogEvent " + dogEvent.getName());
}
if (firstType instanceof CommonSpringEventType.WolfEvent) {
CommonSpringEventType.WolfEvent wolfEvent = (CommonSpringEventType.WolfEvent) firstType;
System.out.println("CommonSpringEventType.WolfEvent " + wolfEvent.getName());
}
}
2.Spring事件异步化
2.1 注入线程池
//来自SimpleApplicationEventMulticaster中的源码
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
由上述源码可以看出,在进行广播前会根据是否有线程池来判断是否走异步。如果这个广播的bean注入了线程池后就会走异步,如果没有就同步执行。
//我们可以自己定义一个bean来注入
@Bean
public Executor executor(){
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 20, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
return executor;
}
@Bean
public SimpleApplicationEventMulticaster applicationEventMulticaster(Executor executor) {
SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new SimpleApplicationEventMulticaster();
simpleApplicationEventMulticaster.setTaskExecutor(executor);
return simpleApplicationEventMulticaster;
}
//上述的applicationEventMulticaster这个bean 名字一定不能改,改了就不能生效,因为源码是根据beanName来找的,原因分析如下:
//这个是初始化applicationEventMulticaster的方法
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
//此处是根据beanName在工厂中确认是否存在这样的bean,如果存在就会拿过来用
//public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
}
}
else {
//如果不存在的话就new一个放到单例池中
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
}
}
注意:如果在上述的applicationEventMulticaster
这个bean中配置了线程池会导致你的项目中的所有事件都是走异步的方式,所以你需要评估事件异步化的风险。因此当你想只针对某个事件使用异步的方式,就是我们下面所要讲的这个注解。
2.2@Async注解
使用@Async注解实行异步化和上述的异步化的逻辑是比较独立,上述是通过判断是否存在线程池,该注解是通过生成JDK动态代理类的方式实现的,因此该方法所对应的类必须存在接口。
//首先要在配置类或者是启动类上加上@EnableAsync注解
//使用也是很简单的就是在方法或者类上加上注解就可以实现异步了
@Component
public class SubscriberClass implements ApplicationListener<CurrentUser> {
private static final Logger log = LoggerFactory.getLogger(SubscriberClass.class);
@Override
@Async
public void onApplicationEvent(CurrentUser currentUser) {
log.info("SubscriberClass + onApplicationEvent + " + currentUser.getName());
}
}
虽然实现了异步,但是Async这个注解实现异步的线程池是有问题的。小编建议大家在使用这个注解时最好使用自己管理的线程池,spring官方给予的默认线程池是是SimpleAsyncTaskExecutor,该线程池默认来一个任务创建一个线程,若系统中不断的创建线程,最终会导致系统占用内存过高,引发OutOfMemoryError错误。当然你也可以设置属性进行限流操作,通常情况下会基于该注解的value值设置自己定义的线程池:
@Bean
public Executor executor(){
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 20, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
return executor;
}
@Component
public class SubscriberClass implements ApplicationListener<CurrentUser> {
private static final Logger log = LoggerFactory.getLogger(SubscriberClass.class);
@Override
@Async("executor") //此处设置对应的线程池bean名字
public void onApplicationEvent(CurrentUser currentUser) {
log.info("SubscriberClass + onApplicationEvent + " + currentUser.getName());
}
}
3.Spring事件原理
先给大家看一个简单的应用:
public class MyEventDemo {
interface MyApplicationEvent{}
interface MyApplicationListener<E extends MyApplicationEvent>{
void handleEvent(E e);
}
//定义事件主体
@Data
public static class EventPayload implements MyApplicationEvent{
private String name;
public EventPayload(String name){
this.name = name;
}
}
//定义第一个监听器
public static class EventFirst implements MyApplicationListener<EventPayload>{
@Override
public void handleEvent(EventPayload eventPayload) {
System.out.println("EventFirst + " + eventPayload.getName());
}
}
//定义第二个监听器
public static class EventSecond implements MyApplicationListener<EventPayload>{
@Override
public void handleEvent(EventPayload eventPayload) {
System.out.println("EventSecond + " + eventPayload.getName());
}
}
//模拟定义一个Spring的内置事件
@Data
public static class SpringInternalPayload implements MyApplicationEvent{
private String path;
public SpringInternalPayload(String path){
this.path = path;
}
}
//模拟定义一个Spring的内置的监听器
public static class SpringInternalEvent implements MyApplicationListener{
@Override
public void handleEvent(MyApplicationEvent myApplicationEvent) {
/**
* 在Spring的内置事件中 你会发现会有这种类型判断的
* 在事件中,一个event的接收器只能处理对应的event类型,如果你在接收事件的地方用某种具体的event类型接收
* 如果别的类型发送来了会报错的,所以在Spring内置事件中会使用接口类型接收 然后做类型判断,不是处理该event的主体类型
* 这个接收器就不会处理
*/
if( myApplicationEvent instanceof SpringInternalPayload){
SpringInternalPayload springInternalPayload = (SpringInternalPayload)myApplicationEvent;
System.out.println("SpringInternalEvent + " + springInternalPayload.getPath());
}
}
}
public static void main(String[] args) {
List<MyApplicationListener> listenerList = Arrays.asList(new EventFirst(), new EventSecond(), new SpringInternalEvent());
EventPayload eventPayload = new EventPayload("Hello");
for(MyApplicationListener listener : listenerList){
listener.handleEvent(eventPayload);
}
}
}
实际上Spring就是基于上面的原理去实现,只不过Spring的内部实现还是很复杂的。大致步骤如下:
-
服务启动时通过实现该接口的类型加载所有监听器; -
事件发送时根据发送的事件主体类型找到对应的监听器(有可能含有Spring内置的监听器); -
对所有监听做执行操作,被执行的监听器如果不是该事件主体类型的监听器会有if类型判断进而不处理
4.总结
本文主要讲述了如何使用Spring的事件方式进行解耦,大致内容总结如下:
-
事件的两种方式:实现接口或者使用@EventListener的注解。 -
事件默认是同步:可以使用@Async注解和配置线程池的方式实现异步; -
使用异步的方式一些注意的事项,比如所有事件会异步/化,需要评估风险; -
通过一个简单的应用介绍了Spring事件的基本原理;
原文始发于微信公众号(处女座码农):Spring事件机制的前世今生
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/251584.html