Spring 的事件监听机制(ApplicationEvent、ApplicationListener)

观察者模式将有依赖关系的对象抽象为了【观察者】和【主题】两个不同的角色,多个【观察者】同时观察一个【主题】,两者只通过【抽象接口】保持松耦合状态,这样双方可以相对独立的进行扩展和变化:比如可以很方便的增删观察者,修改观察者中的更新逻辑而不用修改主题中的代码。

但是这种解耦进行的并不彻底,这具体体现在以下几个方面:

  • 1.抽象主题需要依赖抽象观察者,而这种依赖关系完全可以去除。
  • 2.主题需要维护观察者列表,并对外提供动态增删观察者的接口,
  • 3.主题状态改变时需要由自己去通知观察者进行更新。

我们可以把主题(Subject)替换成事件(event),把对特定主题进行观察的观察者(Observer)替换成对特定事件进行监听的监听器(EventListener),而把原有主题中负责维护主题与观察者映射关系以及在自身状态改变时通知观察者的职责从中抽出,放入一个新的角色事件发布器(EventPublisher)中,事件监听模式的轮廓就展现在了我们眼前,如下图所示

常见事件监听机制的主要角色如下

  • 事件及事件源:对应于观察者模式中的主题。事件源发生某事件时特定事件监听器被触发的原因。
  • 事件监听器:对应于观察者模式中的观察者。监听器监听特定事件,并在内部定义了事件发生后的响应逻辑。
  • 事件发布器:事件监听器的容器,对外提供发布事件和增删事件监听器的接口,维护事件和事件监听器之间的映射关系,并在事件发生时负责通知相关监听器。

监听模式:

要素:

1.事件【及事件源,event里的Object对象】(Event) :继承自 org.springframework.context.ApplicationEvent,可携带数据

2.监听器(Listener): 实现 org.springframework.context.ApplicationListener(或者使用@EventListener注解),用于接收事件、处理件

3.事件发布者 :ApplicaitonContext

使用步骤:

1.定义事件:

public class MySpringApplicationEventForPay extends ApplicationEvent {
   
 /**
还可以定义其他的属性
**/
   private String prop1;
   private String prop2;
   private String prop3;
    

    public MySpringApplicationEventForPay(PaymentInfo source,String prop1,String prop2,String prop3) {
        super(source);

        this.prop1 = prop1;
        this.prop2 = prop2;
        this.prop3 = prop3;
    }
//省略 getter setter
}
2.定义事件监听器类
@Component
public class MySpringApplicationListenerForMail implements ApplicationListener<MySpringApplicationEventForPay> {
    @Override
    public void onApplicationEvent(MySpringApplicationEventForPay event) {
        Object source = event.getSource();
        System.out.println(source+"改变了. 【邮件事件监听器】将要做出响应....");
       
    }
}




@Component
public class MySpringApplicationListenerForSMS implements ApplicationListener<MySpringApplicationEventForPay> {
    
    @Override
    public void onApplicationEvent(MySpringApplicationEventForPay event) {
        Object source = event.getSource();

        System.out.println(source+"改变了. 【短信事件监听器】将要做出响应....");
       
    }
}
注意:定义事件event的时候,不需要用@Compoent注解,而定义监听器实现类的时候需要使用 @Component注解

3.发布事件 

@Component
public class PaymentService {
    
    @Autowired
    ApplicationContext applicationContext;
    
    public void doService(){
        System.out.println(this.getClass().getName() +" 准备做出一些改变,这些改变将会影响到其他的地方");
        
        PaymentInfo paymentInfo = new PaymentInfo(123,"无所谓什么状态了,就是个测试而已");

        MySpringApplicationEventForPay event = new MySpringApplicationEventForPay(paymentInfo);

        applicationContext.publishEvent(event);//发布事件
        
    }
    
}

注意:重点是在 要发布事件的地方,使用 如下代码发布事件,这样事件监听器类才能被触发做出响应

applicationContext.publishEvent(event);//发布事件 

我个人的理解: PaymentService 就是事件源,只不过是在事件源里发布事件时候使用的是applicationContext进行发布的。

本利中额外使用的一个消息 承载对象

public class PaymentInfo {

    private int id;
    private String stauts;

    public PaymentInfo(int id, String stauts) {
        this.id = id;
        this.stauts = stauts;
    }
    // 省略setter getter ....
    @Override
    public String toString() {
        return "PaymentInfo{" +
                "id=" + id +
                ", stauts='" + stauts + '\'' +
                '}';
    }
}

4.测试代码

 PaymentService paymentService = SpringUtil.getBean(PaymentService.class);

 paymentService.doService();

Spring中提供了两个监听器类,一类是无序的,一类是有序的

无序监听器类  ApplicationListener :

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener

有序的监听器类 SmartApplicationListener:

public interface SmartApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered 

spring的事件默认是同步的,即调用publisEvent()方法发布事件后,它会处于阻塞状态,直到onApplicationEvent接收到事件并处理完返回之后才会继续往下执行, 这种单线程同步的好处是可以进行事务管理.

ApplicationContext 会使用ApplicationEventMulticaster作为实际发布事件的委托。 

 protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
        Assert.notNull(event, "Event must not be null");
        if (logger.isTraceEnabled()) {
            logger.trace("Publishing event in " + getDisplayName() + ": " + event);
        }
        // Decorate event as an ApplicationEvent if necessary
        ApplicationEvent applicationEvent;
        if (event instanceof ApplicationEvent) {
            applicationEvent = (ApplicationEvent) event;
        } else {
            applicationEvent = new PayloadApplicationEvent<>(this, event);
            if (eventType == null) {
                eventType = ((PayloadApplicationEvent) applicationEvent).getResolvableType();
            }
        }

        // Multicast right now if possible - or lazily once the multicaster is initialized
        if (this.earlyApplicationEvents != null) {
            this.earlyApplicationEvents.add(applicationEvent);
        } else {
            // 在此处获取ApplicationEventMulticaster并发布,通过multicastEvent
            // 将给定的应用程序事件多路广播给匹配的侦听器
            getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
        }

        // Publish event via parent context as well...
        if (this.parent != null) {
            if (this.parent instanceof AbstractApplicationContext) {
                ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
            } else {
                this.parent.publishEvent(event);
            }
        }
    }

 继续看看multicastEvent(applicationEvent, eventType)方法内是如何发布:
在SimpleApplicationEventMulticaster实现类中具体实现:

    @Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        // 循环获取到的监听此事件的监听器,进行invokeListener
        for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            Executor executor = getTaskExecutor();
            // 线程池不为空的话通过线程池异步调用,默认为空
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            }
            // 默认阻塞调用
            else {
                invokeListener(listener, event);
            }
        }
    }

在invokeListener中调用了doInvokeListener方法,以下是实现:

    private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
        try {
            // 此处是不是熟悉的方法出来了,onApplicationEvent既是自定义监听器中实现了
            // ApplicationListener的onApplicationEvent方法
            listener.onApplicationEvent(event);
        } catch (ClassCastException ex) {
            String msg = ex.getMessage();
            if (msg == null || matchesClassCastMessage(msg, event.getClass().getName())) {
                Log logger = LogFactory.getLog(getClass());
                if (logger.isDebugEnabled()) {
                    logger.debug("Non-matching event type for listener: " + listener, ex);
                }
            } else {
                throw ex;
            }
        }
    }

Spring事件异步化

由于默认线程池是空的,所以在for循环调用监听器时是同步的。我们可以通过设置线程池,改为异步,以使事件发布者和监听者不用在同一个线程中调用。

AbstractApplicationContext类中的refresh()方法中会初始化ApplicationEventMulticaster。下面是初始化代码:

    /**
     * Initialize the ApplicationEventMulticaster.
     * Uses SimpleApplicationEventMulticaster if none defined in the context.
     * @see org.springframework.context.event.SimpleApplicationEventMulticaster
     */
    protected void initApplicationEventMulticaster() {
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        // 判断有没有Bean叫"applicationEventMulticaster",有则使用,没有则创建SimpleApplicationEventMulticaster
        if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
            this.applicationEventMulticaster =
                    beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
            if (logger.isTraceEnabled()) {
                logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
            }
        }
        else {
            this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
            beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
            if (logger.isTraceEnabled()) {
                logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
                        "[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
            }
        }
    }

源码中:spring会加载一个叫applicationEventMulticaster且实现了ApplicationEventMulticaster接口的multicaster,自定义multicaster需要实现了该接口然后将bean的名字设为applicationEventMulticaster即可(名字一定不能修改)。

下面是我们自定义一个事件发布器,并给其注入一个线程池的实现:

@Configuration
public class CustomMulticaster {
    @Bean("applicationEventMulticaster")
    public ApplicationEventMulticaster getApplicationEventMulticaster() {
        SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
        multicaster.setTaskExecutor(Executors.newSingleThreadExecutor());
        return multicaster;
    }
}


 

来自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注入了线程池后就会走异步,如果没有就同步执行。

所以,自定义一个线程池,自定义一个 事件发布器,将自定义的线程池注入到自定义的时间发布器中就可以了 (****上述的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);
        }
    }

自定义一个 线程池 Executor: 

//我们可以自己定义一个bean来注入
   @Bean
    public Executor executor(){
     ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 20, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
     return executor;
    }

 自定义一个发布器 SimpleApplicationEventMulticaster : 

    @Bean
    public SimpleApplicationEventMulticaster applicationEventMulticaster(Executor executor) {
        SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new SimpleApplicationEventMulticaster();
        simpleApplicationEventMulticaster.setTaskExecutor(executor);
        return simpleApplicationEventMulticaster;
    }

注意:如果在上述的applicationEventMulticaster这个bean中配置了线程池会导致你的项目中的所有事件都是走异步的方式,所以你需要评估事件异步化的风险。因此当你想只针对某个事件使用异步的方式,就是我们下面所要讲的这个注解。

@Async注解

使用@Async注解实行异步化和上述的异步化的逻辑是比较独立,上述是通过判断是否存在线程池,该注解是通过生成JDK动态代理类的方式实现的,因此该方法所对应的类必须存在接口。

首先要在配置类或者是启动类上加上@EnableAsync注解
使用也是很简单的就是在方法或者类上加上注解就可以实现异步了

//首先要在配置类或者是启动类上加上@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());
    }
}

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值