Spring事件监听机制的使用和原理解析

Spring的context包是对于beans包的功能扩展,比如在BeanFactory的基础容器之上扩展为了ApplicationContext上下文。而ApplicationContext处理包含了BeanFactory的全部基础功能之外,还额外提供了大量的扩展功能,本文主要分析下Spring提供的事件监听机制,这里就使用到了设计模式中的观察者设计模式。话不多说,直接正文

Spring事件监听机制的定义

使用过MQ的或者了解观察者设计模式的同学应该大致都了解,实现事件监听机制至少四个核心:事件、事件生产者和事件消费者,另外还需要有一个管理生产者、消费者和事件之间的注册监听关系的控制器。
在Spring中,事件监听机制主要实现是通过事件、事件监听器、事件发布者和事件广播器来实现

Spring中的事件(ApplicationEvent)

spring中的事件有一个抽象父类ApplicationEvent,该类包含有当前ApplicationContext的引用,这样就可以确认每个事件是从哪一个Spring容器中发生的

Spring中的事件监听器(ApplicationListener)

spring中的事件监听器同样有一个顶级接口ApplicationListener,只有一个onApplicationEvent(E event)方法,当该监听器所监听的事件发生时,就会执行该方法

Spring中的事件发布者(ApplicationEventPublisher)

spring中的事件发布者同样有一个顶级接口ApplicationEventPublisher,只有一个方法publishEvent(Object event)方法,调用该方法就可以发生spring中的事件

Spring中的事件广播器(ApplicationEventMulticaster)

spring中的事件核心控制器叫做事件广播器,接口为ApplicationEventMulticaster,广播器的作用主要有两个:

作用一:将事件监听器注册到广播器中,这样广播器就知道了每个事件监听器分别监听什么事件,且知道了每个事件对应哪些事件监听器在监听

作用二:将事件广播给事件监听器,当有事件发生时,需要通过广播器来广播给所有的事件监听器,因为生产者只需要关心事件的生产,而不需要关心该事件都被哪些监听器消费

Spring事件监听机制的使用

以电商为例,假设现在有这样一个场景:当用户下单成功之后,此时需要做很多操作,比如需要保存一个订单记录、对应的商品库存需要扣除,假设下单的时候还用到了红包,那么对应的红包也需要改成已经使用。所以相当于一个下单操作,需要进行三个数据更新操作。而这三个操作实际上又是互相没有任何关联的,所以可以通过三个下单事件的监听器分别来处理对应的业务逻辑,此时就可以采用Spring的事件监听机制来模拟实现这样的场景。
1、首先定义一个下单事件 OrderEvent,下单事件中包含了订单号、商品编号和使用的红包编号,代码如下

/**
 * @Auther: Lucky
 * @Date: 2020/7/8 下午2:53
 * @Desc: 自定义下单事件
 */
public class OrderEvent extends ApplicationEvent {

    /** 订单编号*/
    private String orderCode;
    /** 商品编号*/
    private String goodsCode;
    /** 红包编号*/
    private String redPacketCode;

    /** 事件的构造函数*/
    public OrderEvent(ApplicationContext source, String orderCode, String goodsCode, String redPacketCode) {
        super(source);
        this.orderCode = orderCode;
        this.goodsCode = goodsCode;
        this.redPacketCode = redPacketCode;
    }
}

2、分别定义订单监听器、商品监听器和红包监听器分别监听下单事件,分别做对应的处理,代码如下

1  /**
 2   * @Desc: 订单监听器(用于保存订单信息)
 3   */
 4 public class OrderListener implements ApplicationListener<OrderEvent>  {
 5 
 6     @Override
 7     public void onApplicationEvent(OrderEvent event) {
 8         System.out.println("订单监听器监听到下单事件,订单号为:" + event.getOrderCode());
 9         //TODO 保存订单处理11     }
12 }
1 /**
 2  * @Desc: 商品监听器(用于更新商品的库存)
 3  */
 4 public class GoodsListener implements ApplicationListener<OrderEvent> {
 5 
 6     @Override
 7     public void onApplicationEvent(OrderEvent event) {
 8         System.out.println("商品监听器监听到下单事件,更新商品库存:" + event.getGoodsCode());
 9         //TODO 更新商品库存11     }
12 }
/**
 * @Desc: 红包监听器(用于使用红包)
 */
public class RedPacketListener implements ApplicationListener<OrderEvent> {

    @Override
    public void onApplicationEvent(OrderEvent event) {
        if(event.getRedPacketCode()!=null) {
            System.out.println("红包监听器监听到下单事件,红包编号为:" + event.getRedPacketCode());
            //TODO 使用红包处理
        }else {
            System.out.println("订单:"+event.getOrderCode()+"没有使用红包");
        }
    }
}

3、事件发布器和事件广播器无需自定义,采用Spring默认的就可以

4、现在事件、事件监听器、事件发布器和事件广播器都已经定义完毕,接下来就需要将事件监听器和事件的监听关系注册到Spring容器中即可,方法很简单,实际就是将事件监听器当作是一个bean注册到Spring容器即可,如下示:

<beans>

   <!-- 其他bean -->

    <bean id="orderListener" class="com.lucky.test.spring.event.OrderListener"/>
    <bean id="goodsListener" class="com.lucky.test.spring.event.GoodsListener"/>
    <bean id="redPacketListener" class="com.lucky.test.spring.event.RedPacketListener"/>
</beans>

5.测试代码如下:

public static void main(String[] args){
        /** 1.初始化Spring容器 */
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        /** 2. 模拟创建下单事件*/
        for (int i=0;i < 5;i++){
            String orderCode = "test_order_" + i;
            String goodsCode = "test_order_" + i;
            String redPacketCode = null;
            if(i%2==0) {
                //偶数时使用红包
                redPacketCode = "test_order_" + i;
            }
            OrderEvent orderEvent = new OrderEvent(context, orderCode, goodsCode, redPacketCode);
            /**3. ApplicationContext实现了ApplicationEventPublisher接口,所以可以直接通过ApplicationContext来发送事件*/
            context.publishEvent(orderEvent);
        }
    }

测试代码很简单,第一步是先初始化Spring容器,第二步是创建下单事件,第三部就是通过ApplicationContext直接发送事件,此时三个事件监听器就可以监听到下单事件并处理了,测试结果如下:

订单监听器监听到下单事件,订单号为:test_order_0
商品监听器监听到下单事件,更新商品库存:test_order_0
红包监听器监听到下单事件,红包编号为:test_order_0

订单监听器监听到下单事件,订单号为:test_order_1
商品监听器监听到下单事件,更新商品库存:test_order_1
订单:test_order_1没有使用红包

订单监听器监听到下单事件,订单号为:test_order_2
商品监听器监听到下单事件,更新商品库存:test_order_2
红包监听器监听到下单事件,红包编号为:test_order_2

订单监听器监听到下单事件,订单号为:test_order_3
商品监听器监听到下单事件,更新商品库存:test_order_3
订单:test_order_3没有使用红包

订单监听器监听到下单事件,订单号为:test_order_4
商品监听器监听到下单事件,更新商品库存:test_order_4
红包监听器监听到下单事件,红包编号为:test_order_4
Tip:Spring的事件监听机制是同步处理的,也就是说生产者发布事件和消费者消费事件是在同一个线程下执行的,所以本案例中的下单事件虽然按三个事件监听器分别监听下单事件,但是总的方法耗时并没有减少,并且如果任何一个监听器抛了异常,同样会影响到其他的监听器,所以每个事件监听器监听消息时必须要对事件进行异常捕获操作,或者内部改成异步处理。

比如将监听器改造如下:

1.在发布事件和消费事件出分别打印当前线程;2.红包监听器不做非空判断,会导致redPacketCode为空时抛异常,测试结果如下:

发布事件线程为:main
订单监听器监听到下单事件,订单号为:test_order_0线程为:main
商品监听器监听到下单事件,更新商品库存:test_order_0线程为:main
红包监听器监听到下单事件,更新红包:test_order_0线程为:main
发布事件线程为:main
订单监听器监听到下单事件,订单号为:test_order_1线程为:main
商品监听器监听到下单事件,更新商品库存:test_order_1线程为:main
红包监听器监听到下单事件,更新红包:null线程为:main
Exception in thread "main" java.lang.NullPointerException
    at com.lucky.test.spring.event.RedPacketListener.onApplicationEvent(RedPacketListener.java:15)
    at com.lucky.test.spring.event.RedPacketListener.onApplicationEvent(RedPacketListener.java:10)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:403)
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:360)
    at com.lucky.test.spring.MainTest.main(MainTest.java:29)

从测试结果可以看出,发布事件和所有消费事件的监听器执行的线程都是同一个线程,也就是本案例中的main线程,所以Spring的整个事件监听机制实际上也是同步操作,都是由发布事件的线程来处理的。

而且一旦某一个监听器抛异常了,就相当于整个线程都中止了,不仅后面的监听器无法消费事件,连发布事件的线程也会收到影响。当多个监听器之间的业务逻辑互相不影响时,可以采用优化方案,如下图示:
在这里插入图片描述

此时就可以将事件监听器改成异步处理方式,也就是同步接收事件消息,但是处理业务逻辑改成异步处理,比如上例中的订单监听器改造如下:

public class OrderListener implements ApplicationListener<OrderEvent>  {

    @Override
    public void onApplicationEvent(OrderEvent event) {
        /** 通过try/catch保证事件监听器不会抛异常*/
        try {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("订单监听器监听到下单事件,订单号为:" + event.getOrderCode() + "线程为:" + Thread.currentThread().getName());
                    //TODO 保存订单处理
                }
            }).start();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

通过新建线程的方式去异步执行业务逻辑,或者将业务逻辑交给线程池执行也行。

5.对于同一个事件如果有多个事件监听器时,既然是同步的,那么就必然会有执行顺序的区别,Spring默认的事件执行的执行顺序是按照bean加载的顺序执行的,比如本例中,在XML中配置的顺序是OrderListener->GoodsListener->RedPacketListener,

那么最后执行的顺序就是这个顺序,但是很显然这种隐式的排序方式很容易让开发人员忽视,所以Spring提供了额外的排序方式,就是让监听器实现Ordered接口或者Ordered的子接口PriorityOrdered接口

Ordered接口只有一个方法

/** 最高优先级*/
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;

/** 最低优先级*/
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;

/** 获取优先级值,值越小优先级越高*/
int getOrder();

而Ordered接口的子接口PriorityOrdered继承之Ordered接口,但是没有任何实现,也就是说PriorityOrdered的作用仅仅就是一个标识的作用。

在Spring事件监听机制中,优先级是优先按PriorityOrdered排序,然后再按Ordered排序,最终就按bean的加载顺序排序

比如上例中的三个监听器,由于OrderListener是最先加载的,所以默认是最先执行的,此时我们分别在GoodsListener和RedPacketListener实现Ordered接口和PriorityOrdered接口,分别如下:

public class GoodsListener implements ApplicationListener<OrderEvent>, Ordered {
    @Override
    public int getOrder() {
        /** 使用最高优先级*/
        return Ordered.HIGHEST_PRECEDENCE;
    }
}
public class RedPacketListener implements ApplicationListener<OrderEvent>, PriorityOrdered {

    @Override
    public int getOrder() {
        /**使用最低优先级*/
        return Ordered.LOWEST_PRECEDENCE;
    }
}

这里GoodsListener实现了Ordered接口,优先级为最高优先级;RedPacketListener实现了PriorityOrdered,设置优先级为最低优先级,执行结果如下:

1 红包监听器监听到下单事件,更新红包:test_order_0
2 商品监听器监听到下单事件,更新商品库存:test_order_0
3 订单监听器监听到下单事件,订单号为:test_order_0

可以发现实现了PriorityOrdered接口的RedPacketListener最先执行,实现了Ordered接口的GoodsListener第二个执行,没有实现排序接口的OrderListener接口最后执行。

这里虽然RedPacketListener和GoodsListener都实现了getOrder()方法,并且GoodsListener设置为优先级最高和RedPacketListener的优先级最低,但是还是会先执行RedPacketListener,这就是实现了PriorityOrdered接口的优势。

实现了PriorityOrdered接口的监听器无论优先级值如何都肯定会在实现了Ordered接口的监听器优先执行,这也就是PriorityOrdered接口没有任何实现的原因,这个接口仅仅是为了标记的作用,标记这个监听器是最优先执行的。

Spring事件监听机制的实现原理

通过上面的例子,已经了解了事件和事件监听器的处理逻辑,但是还有很多问题需要去探究,比如事件的发布过程、监听器是如何监听消息的等等,此时就需要从ApplicationContext的初始化开始说起了。

当ApplicationContext初始化的时候, 有两个核心步骤和事件监听器有关,一个是初始化事件广播器,一个是注册所有的事件监听器

/** 其他流程*/

/** 初始化事件广播器*/
initApplicationEventMulticaster();

/**其他流程*/

/** 注册事件监听器*/
registerListeners();

/** 其他流程*/

3.1、初始化事件广播器源码解析

/** Spring容器的事件广播器对象*/
    private ApplicationEventMulticaster applicationEventMulticaster;

    /** 事件广播器对应的beanName*/
    public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";

    /** 初始化事件广播器*/
    protected void initApplicationEventMulticaster() {
        //1.获取Spring容器BeanFactory对象
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        //2.从BeanFactory获取事件广播器的bean,如果存在说明是用户自定义的事件广播器
        if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
            //2.1.给容器的事件广播器赋值
            this.applicationEventMulticaster =
                    beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
            if (logger.isTraceEnabled()) {
                logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
            }
        }
        else {
            //3.如果没有自定义的,则初始化默认的事件广播器SimpleApplicationEventMulticaster对象
            this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
            //4.注册该bean
            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() + "]");
            }
        }
    }

从源码可以看出,初始化事件广播器的逻辑比较简单,就是先给容器的事件广播器applicationEventMulticaster对象赋值的过程,如果beanFactory中存在用于自定义的就使用自定义的,如果没有自定义的就创建新的默认的事件广播器SimpleApplicationEventMulticaster对象,然后赋值给applicationEventMulticaster对象。

3.2、注册事件监听器源码解析

/** 注册事件监听器*/
    protected void registerListeners() {
        //1.遍历将通过编码方式创建的事件监听器加入到事件广播器中
        for (ApplicationListener<?> listener : getApplicationListeners()) {
            //2.获取到当前事件广播器,添加事件监听器
            getApplicationEventMulticaster().addApplicationListener(listener);
        }

        //3.从BeanFactory中获取所有实现了ApplicationListener接口的bean,遍历加入到事件广播器中
        String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
        for (String listenerBeanName : listenerBeanNames) {
            getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
        }

        //3.获取需要提前发布的事件
        Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
        this.earlyApplicationEvents = null;
        if (earlyEventsToProcess != null) {
            for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
                //5.遍历将提前发布的事件广播出去
                getApplicationEventMulticaster().multicastEvent(earlyEvent);
            }
        }

从源码上看,注册事件监听器的逻辑也不复杂,主要是从容器中找到所有的事件监听器,然后调用事件广播器的addApplicationListener方法将事件监听器添加到事件广播器中,接下来再看下事件广播器添加到逻辑,源码如下:

1 @Override
 2     public void addApplicationListener(ApplicationListener<?> listener) {
 3         synchronized (this.retrievalMutex) {
 4             // 将事件监听器加入的内部的监听器集合applicationListeners中
 6             Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
 7             if (singletonTarget instanceof ApplicationListener) {
 8                 this.defaultRetriever.applicationListeners.remove(singletonTarget);
 9             }
10             this.defaultRetriever.applicationListeners.add(listener);
11             this.retrieverCache.clear();
12         }
13     }

可以看出注册的逻辑比较简单,就是将Listener对象加入到事件广播器内部的集合中保存起来,这样事件广播器就保存了容器中所有的事件监听器了。

3.3、事件的发布和消费原理

事件的发布是通过ApplicationEventPublisher的实现类实现的publishEvent方法实现的,ApplicationContext就实现了该接口,所以使用Spring时就可以直接使用ApplicationContext实例来调用publishEvent方法来发布事件,源码如下:

/** 发布事件
     * @param event:事件对象
     *  */
    @Override
    public void publishEvent(Object event) {
        publishEvent(event, null);
    }

    /** 发布事件
     * @param event:事件对象
     * @param eventType:事件类型
     * */
    protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
        Assert.notNull(event, "Event must not be null");

        /** 1.将发布的事件封装成ApplicationEvent对象(因为传入的参数是Object类型,有可能没有继承ApplicationEvent) */
        ApplicationEvent applicationEvent;
        if (event instanceof ApplicationEvent) {
            applicationEvent = (ApplicationEvent) event;
        }
        else {
            applicationEvent = new PayloadApplicationEvent<>(this, event);
            if (eventType == null) {
                eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
            }
        }

        if (this.earlyApplicationEvents != null) {
            /** 2.1.如果需要提前发布的事件还没有发布完,则不是立即发布,而是将事件加入到待发布集合中*/
            this.earlyApplicationEvents.add(applicationEvent);
        }
        else {
            /** 2.2.获取当前的事件广播器,调用multicasterEvent方法广播事件*/
            getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
        }

        /** 3.如果当前applicationContext有父类,则再调用父类的publishEvent方法*/
        if (this.parent != null) {
            if (this.parent instanceof AbstractApplicationContext) {
                ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
            }
            else {
                this.parent.publishEvent(event);
            }
        }
    }

其实这里的逻辑也比较简单,首先是将发布的事件转化成ApplicationEvent对象,然后获取到事件广播器,调用事件广播器的multicastEvent方法来广播事件,所以核心逻辑又回到了事件广播器那里

/** 广播事件
     * @param event:事件
     * @param eventType:事件类型
     * */
    @Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        Executor executor = getTaskExecutor();(如果有Executor,则广播事件就是通过异步来处理的)
        /**
         * 1.根据事件和类型调用getApplicationListeners方法获取所有监听该事件的监听器
         * */
        for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            if (executor != null) {
                /** 2. 异步遍历执行invokeListener方法来唤醒监听器处理事件 */
                executor.execute(() -> invokeListener(listener, event));
            }
            else {
                invokeListener(listener, event);
            }
        }
    }

这里主要有两个核心步骤,首先是根据事件和类型找到监听了该事件的所有事件监听器;然后遍历来执行监听器的处理逻辑.另外如果配置了执行器Executor,就会通过Executor来异步发布事件给监听器

1、根据事件获取事件监听器源码如下

protected Collection<ApplicationListener<?>> getApplicationListeners(
            ApplicationEvent event, ResolvableType eventType) {

        Object source = event.getSource();
        Class<?> sourceType = (source != null ? source.getClass() : null);
        ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);

        // Quick check for existing entry on ConcurrentHashMap...
        ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
        if (retriever != null) {
            return retriever.getApplicationListeners();
        }

        if (this.beanClassLoader == null ||
                (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
                        (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
            // Fully synchronized building and caching of a ListenerRetriever
            synchronized (this.retrievalMutex) {
                retriever = this.retrieverCache.get(cacheKey);
                if (retriever != null) {
                    return retriever.getApplicationListeners();
                }
                retriever = new ListenerRetriever(true);
                Collection<ApplicationListener<?>> listeners =
                        retrieveApplicationListeners(eventType, sourceType, retriever);
                this.retrieverCache.put(cacheKey, retriever);
                return listeners;
            }
        }
        else {
            // No ListenerRetriever caching -> no synchronization necessary
            return retrieveApplicationListeners(eventType, sourceType, null);
        }
    }

这里核心方法是retrieveApplicationListeners(eventType, sourceType, retriever)方法,源码如下:

private Collection<ApplicationListener<?>> retrieveApplicationListeners(
            ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever) {

        List<ApplicationListener<?>> allListeners = new ArrayList<>();
        Set<ApplicationListener<?>> listeners;
        Set<String> listenerBeans;
        synchronized (this.retrievalMutex) {               /** 初始化所有事件监听器,存入集合中*/
            listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
            listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
        }

        // Add programmatically registered listeners, including ones coming
        // 遍历所有监听器,调用supportsEvent判断是否监听该事件
        for (ApplicationListener<?> listener : listeners) {
            if (supportsEvent(listener, eventType, sourceType)) {
                if (retriever != null) {
                    retriever.applicationListeners.add(listener);
                }                   /** 如果监听器监听当前事件,则加入到监听器集合中*/
                allListeners.add(listener);
            }
        }

        // Add listeners by bean name, potentially overlapping with programmatically
        // registered listeners above - but here potentially with additional metadata.
        if (!listenerBeans.isEmpty()) {
            ConfigurableBeanFactory beanFactory = getBeanFactory();
                       //
            for (String listenerBeanName : listenerBeans) {
                try {
                    if (supportsEvent(beanFactory, listenerBeanName, eventType)) {
                        ApplicationListener<?> listener =
                                beanFactory.getBean(listenerBeanName, ApplicationListener.class);
                        if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
                            if (retriever != null) {
                                if (beanFactory.isSingleton(listenerBeanName)) {
                                    retriever.applicationListeners.add(listener);
                                }
                                else {
                                    retriever.applicationListenerBeans.add(listenerBeanName);
                                }
                            }
                            allListeners.add(listener);
                        }
                    }
                    else {
                        // Remove non-matching listeners that originally came from
                        // ApplicationListenerDetector, possibly ruled out by additional
                        // BeanDefinition metadata (e.g. factory method generics) above.
                        Object listener = beanFactory.getSingleton(listenerBeanName);
                        if (retriever != null) {
                            retriever.applicationListeners.remove(listener);
                        }
                        allListeners.remove(listener);
                    }
                }
                catch (NoSuchBeanDefinitionException ex) {
                    // Singleton listener instance (without backing bean definition) disappeared -
                    // probably in the middle of the destruction phase
                }
            }
        }

                /** 将所有监听器根据Order进行排序*/
        AnnotationAwareOrderComparator.sort(allListeners);
        if (retriever != null && retriever.applicationListenerBeans.isEmpty()) {
            retriever.applicationListeners.clear();
            retriever.applicationListeners.addAll(allListeners);
        }
        return allListeners;
    }

代码比较多,但是核心步骤其实就三步,

第一步:获取事件广播器中所有的事件监听器

第二步:遍历事件监听器,判断该监听器是否监听当前事件

第三步:将所有监听当前事件的监听器进行排序

其中第二步判断监听器是否监听事件的判断,主要是通过反射获取该监听器实现的接口泛型类,如果包含当前事件的类则表示监听,否则就表示不监听

2、唤醒监听器处理事件源码如下:

protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
        ErrorHandler errorHandler = getErrorHandler();
        if (errorHandler != null) {
            try {
                /** 调用doInvokeListener方法*/
                doInvokeListener(listener, event);
            }
            catch (Throwable err) {
                errorHandler.handleError(err);
            }
        }
        else {
            /** 调用doInvokeListener方法*/
            doInvokeListener(listener, event);
        }
    }
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
        try {
/** 直接调用ApplicationListener的onApplicationEvent(event)方法*/
            listener.onApplicationEvent(event);
        }
        catch (ClassCastException ex) {
            String msg = ex.getMessage();
            if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
                // Possibly a lambda-defined listener which we could not resolve the generic event type for
                // -> let's suppress the exception and just log a debug message.
                Log logger = LogFactory.getLog(getClass());
                if (logger.isTraceEnabled()) {
                    logger.trace("Non-matching event type for listener: " + listener, ex);
                }
            }
            else {
                throw ex;
            }
        }
    }

可以看出唤醒的逻辑比较简单,直接调用监听器的onApplicationEvent(E event)方法即可。

Spring事件发布监听机制实现业务解耦

上面说的这么多都是源码解析,接下来直接步入实战阶段

引言

假设一个下单场景,订单创建成功后可能有一些后续逻辑要处理,但是和创建订单本身没有关系,此时就可以在创建订单完成后,发送一个消息,有相应部分的代码进行监听处理,避免代码耦合到一起

这样的解决思路类似于MQ,但是小项目有时候又不需要MQ这样的第三方队列来实现,那么就可以使用Spring Context包的事件发布监听的机制来进行处理

Spring事件发布监听机制

在这里插入图片描述

流程: 当事件源(发布者)发布事件时,相应监听此事件的监听者接收到事件对象并且进行处理

Spring的事件发布监听机制本质上就是发布-订阅,即生产者-消费者,也体现了设计模式中的观察者模式

三要素

  • ApplicationEvent:事件
  • ApplicationListener:事件监听者
  • ApplicationEventPublisher:事件发布者

事件(ApplicationEvent)

消息类

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class Message {

    private Long messageId;

    private String content;

}

事件包含的实体类:事件类:

@Getter
@Setter
public class MessageEvent extends ApplicationEvent {

    private static final long serialVersionUID = 4181929072911659524L;

    private Message message;

    public MessageEvent(Message message) {
        super(message);
        this.message = message;
    }

}

事件类,继承了ApplicationEvent,并且包含了传递实体类Message
MessageEvent的关系类图(Diagram):
在这里插入图片描述

其中ApplicationEvent的源码:

/**
 * Class to be extended by all application events. Abstract as it
 * doesn't make sense for generic events to be published directly.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @see org.springframework.context.ApplicationListener
 * @see org.springframework.context.event.EventListener
 */
public abstract class ApplicationEvent extends EventObject {

	/** use serialVersionUID from Spring 1.2 for interoperability. */
	private static final long serialVersionUID = 7099057708183571937L;

	/** System time when the event happened. */
	private final long timestamp;


	/**
	 * Create a new {@code ApplicationEvent}.
	 * @param source the object on which the event initially occurred or with
	 * which the event is associated (never {@code null})
	 */
	public ApplicationEvent(Object source) {
		super(source);
		this.timestamp = System.currentTimeMillis();
	}


	/**
	 * Return the system time in milliseconds when the event occurred.
	 */
	public final long getTimestamp() {
		return this.timestamp;
	}

}

可以看出ApplicationEvent有记录发生event的时间,并且source的意义就是当做发布事件的实体类

事件监听者(ApplicationListener)

事件监听类:方式一(EventListener注解实现)

@Component
@Slf4j
public class MessageListener {


    @EventListener(value = MessageEvent.class)
    public void listen(MessageEvent event){
        log.info("\n██listener1线程:{}",Thread.currentThread().getThreadGroup()+ "/" +Thread.currentThread().getName());
        log.info("event:{}",event);
        // 处理逻辑
    }

}

注解实现监听的原理:
1:查看@EventListener注解的调用链,其中EventListenerMethodProcessor类的processBean方法(1)

// 截取的代码
Map<Method, EventListener> annotatedMethods = null;
try {
  annotatedMethods = MethodIntrospector.selectMethods(targetType,
      (MethodIntrospector.MetadataLookup<EventListener>) method ->
          AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
}
catch (Throwable ex) {
  // An unresolvable type in a method signature, probably from a lazy bean - let's ignore it.
  if (logger.isDebugEnabled()) {
    logger.debug("Could not resolve methods for bean with name '" + beanName + "'", ex);
  }
}

获取所有被@EventListener注解修饰的Listener

2:EventListenerMethodProcessor类的processBean方法(2)

// 截取的代码
// Non-empty set of methods
ConfigurableApplicationContext context = this.applicationContext;
    Assert.state(context != null, "No ApplicationContext set");
List<EventListenerFactory> factories = this.eventListenerFactories;
    Assert.state(factories != null, "EventListenerFactory List not initialized");
    for (Method method : annotatedMethods.keySet()) {
    for (EventListenerFactory factory : factories) {
        if (factory.supportsMethod(method)) {
            Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
            ApplicationListener<?> applicationListener =
                    factory.createApplicationListener(beanName, targetType, methodToUse);
            if (applicationListener instanceof ApplicationListenerMethodAdapter) {
                ((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
            }
            context.addApplicationListener(applicationListener);
            break;
        }
    }
}

使用Listener Factory类生产出所有被@EventListener的类注入进Spring Context

事件监听类:方式二(实现ApplicationListener接口)

@Component
@Slf4j
public class MessageListener2 implements ApplicationListener<MessageEvent> {

    @Override
    public void onApplicationEvent(MessageEvent event) {
        log.info("\n██listener2线程:{}",Thread.currentThread().getThreadGroup()+ "/" +Thread.currentThread().getName());
        log.info("event:{}",event);
        // 处理逻辑
    }
}

实现接口来实现监听的原理:

1:ApplicationListenerDetector的postProcessAfterInitialization方法

@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) {
		if (bean instanceof ApplicationListener) {
			// potentially not detected as a listener by getBeanNamesForType retrieval
			Boolean flag = this.singletonNames.get(beanName);
			if (Boolean.TRUE.equals(flag)) {
				// singleton bean (top-level or inner): register on the fly
				this.applicationContext.addApplicationListener((ApplicationListener<?>) bean);
			}
			else if (Boolean.FALSE.equals(flag)) {
				if (logger.isWarnEnabled() && !this.applicationContext.containsBean(beanName)) {
					// inner bean with other scope - can't reliably process events
					logger.warn("Inner bean '" + beanName + "' implements ApplicationListener interface " +
							"but is not reachable for event multicasting by its containing ApplicationContext " +
							"because it does not have singleton scope. Only top-level listener beans are allowed " +
							"to be of non-singleton scope.");
				}
				this.singletonNames.remove(beanName);
			}
		}
		return bean;
	}

判断一个Bean如果是ApplicationListener,则也是使用context.addApplicationListener添加

事件发布者(ApplicationEventPublisher)

@Autowired
private ApplicationContext applicationContext;

@PostMapping("/sendMessage")
public String sendMessage(){
    log.info("\n██Test线程:{}",Thread.currentThread().getThreadGroup()+ "/" +Thread.currentThread().getName());
    Message newMessage = Message.builder()
            .messageId(20200610111500000L)
            .content("消息内容").build();
    MessageEvent event = new MessageEvent(newMessage);
    // 事件发布
    applicationContext.publishEvent(event);
    return "消息发送成功";
}

事件发布原理分析:

1:查看ApplicationContext类

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
		MessageSource, ApplicationEventPublisher, ResourcePatternResolver {

2:AbstractApplicationContext类的publishEvent方法

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
		Assert.notNull(event, "Event must not be null");

		// 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 {
			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);
			}
		}
	}

getApplicationEventMulticaster().multicastEvent方法可以看出事件是通过SimpleApplicationEventMulticaster的multicastEvent方法发布的

3:SimpleApplicationEventMulticaster的multicastEvent方法

@Override
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);
    }
  }
}

支持异步

设置Executor的方法实现异步(推荐)

@Slf4j
@Configuration
public class EventConfig {

    @Bean(name = "applicationEventMulticaster")
    public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
        SimpleApplicationEventMulticaster eventMulticaster
                = new SimpleApplicationEventMulticaster();

        eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
        return eventMulticaster;
    }

}

异步注解(不推荐)

1:主类新增@EnableAsync注解开启异步

@SpringBootApplication
@EnableAsync
public class SpringEventDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringEventDemoApplication.class, args);
    }
    
}

2:监听类监听方法新增注解@Async

@Component
@Slf4j
public class MessageListener {


    @Async
    @EventListener(value = MessageEvent.class)
    public void listen(MessageEvent event){
        ThreadUtil.sleep(6000);
        log.info("\n██listener1线程:{}",Thread.currentThread().getThreadGroup()+ "/" +Thread.currentThread().getName());
        log.info("event:{}",event);
    }

}

创建新的线程(不推荐)

/**
 * 订单服务
 */
@Service
public class OrderService {

    @Autowired
    private ApplicationContext applicationContext;

    public void order() {
        // 下单成功
        System.out.println("下单成功...");
        // 发布通知
        new Thread(() ->{
            applicationContext.publishEvent(new LendSuccesssEvent(this));
        }).start();
        System.out.println("main线程结束...");
        // 等SmsService结束
        try {
            Thread.sleep(1000L * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出:
下单成功…
main线程结束…
发送短信…
这种方式就和加异步注解一个意思,如下图,违背了Spring事件机制的设计初衷
在这里插入图片描述

总结

  • 相对于实现ApplicationListener接口来监听事件的方式,使用注解的方式更加简便,并且方式二一个监听类只能监听一个事件,方式一则可新增方法来监听多个其他的事件
  • 可以看出如果设置了Executor(线程池)的话,则异步执行监听方法,否则执行同步方法
    所以后续可以用设置Executor的方法实现异步
  • 实现异步有2中 但是为什么推荐线程池呢,因为使用另外一种其实有点违背了Spring事件机制的设计初衷,所以推荐第一种做法实现异步
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值