面了个35的程序员,让我莫名的慌了。。。

月底免费送书活动,这两天是最后的机会,你们尽快参与!java

面试官:看你是85年的mysql

我:嗯,35了git

面试官:那应该经验很丰富了,那咱们来聊聊spring吧面试

我:好,这块我用了10几年了,你随便问吧spring

面试官:Spring中的事件用过么?sql

我:用过缓存

面试官:能够介绍一下为何须要使用事件么?服务器

我:使用事件的模式能够对系统进行解耦,事件源发布一个事件,事件监听器能够消费这个事件,而事件源不用关注发布的事件有哪些监听器,这能够能够对系统进行解耦mybatis

面试官:Spring事件的实现有几种方式?并发

我:总体来讲2种方式,第一种是经过接口的方式,第二种是在方法上使用注解的方式

面试官:Spring中事件监听器的处理是同步方式仍是异步方式?

我:很差意思,没听懂问题

面试官:事件的发布和事件监听器的执行是否在同一个线程中运行?

我:在一个线程中执行,是同步的方式

面试官:是否支持异步方式?

我:支持

面试官:你肯定么?

我:嗯。。。,这块没有用过,不过我感受是能够的,事件监听器中的逻辑通常不是主要业务,能够再也不当前线程中执行。

面试官:那spring中事件监听器支持自定义顺序么?

我:这个不知道

面试官:行吧,今天的面试到此为止吧,回去以后巩固下本身的技术,多看看源码,不要荒废了,否则会愈来愈难

我:好的。。。。此时脑子里已是浆糊了。

回去以后赶忙将spring事件这块源码翻出来又好好研究了几遍。

面试过程当中的主要问题

  1. 为何须要使用事件这种模式?

  2. spring中实现事件有几种方式?

  3. spring中事件监听器消费事件是否支持异步模式?

  4. spring中事件监听器消费事件是否支持自定义顺序?

下面咱们就一个个来介绍。

为何须要使用时间这种模式?

先来看一个业务场景:

产品经理:路人,这两天你帮我实现一个注册的功能

我:注册功能比较简单,将用户信息入库就能够了,伪代码以下:

public void registerUser(UserModel user){
    //插入用户信息到db,完成注册
    this.insertUser(user);
}

过了几天,产品经理:路人,注册成功以后,给用户发送一封注册成功的邮件

我:修改了一下上面注册代码,以下:

public void registerUser(UserModel user){
    //插入用户信息到db,完成注册
    this.insertUser(user);
    //发送邮件
    this.sendEmailToUser(user);
}

因为修改了注册接口,因此全部调用这个方法的地方都须要从新测试一遍,让测试的兄弟们帮忙跑了一遍。

又过了几天,产品经理:路人,注册成功以后,给用户发一下优惠券

我:好的,又调整了一下代码

public void registerUser(UserModel user){
    //插入用户信息到db,完成注册
    this.insertUser(user);
    //发送邮件
    this.sendEmailToUser(user);
    //发送优惠券
    this.sendCouponToUser(user);
}

我:测试的兄弟们,辛苦一下你们,注册接口又修改了,帮忙再过一遍。

过了一段时间,公司效益太好,产品经理:路人,注册的时候,取消给用户发送优惠券的功能。

我:又跑去调整了一下上面代码,将发送优惠券的功能干掉了,以下

public void registerUser(UserModel user){
    //插入用户信息到db,完成注册
    this.insertUser(user);
    //发送邮件
    this.sendEmailToUser(user);
}

因为调整了代码,而注册功能又属于核心业务,因此须要让测试再次帮忙过一遍,又要麻烦测试来一遍了。

忽然有一天,产品经理:路人,注册接口怎么这么慢啊,而且还常常失败?你这让公司要损失多少用户啊

我:赶忙跑去查看了一下运行日志,发现注册的时候给用户发送邮件不稳定,依赖于第三方邮件服务器,耗时比较长,而且容易失败。

跑去给产品经理说:因为邮件服务器不稳定的缘由,致使注册不稳定。

产品经理:邮件你能够不发,可是你得确保注册功能必须能够用啊。

我想了想,将上面代码改为了下面这样,发送邮件放在了子线程中执行:

public void registerUser(UserModel user){
    //插入用户信息到db,完成注册
    this.insertUser(user);
    //发送邮件,放在子线程中执行,邮件的发送结果对注册逻辑不会有干扰做用
    new Thread(()->{
        this.sendEmailToUser(user);
    }).start();
}

又过了几天,产品经理又跑来了说:路人,最近效益很差,须要刺激用户消费,注册的时候继续发送优惠券。

我:倒,这是玩我么,反反复复让我调整注册的代码,让我改还好,让测试也反反复复来回搞,这是要玩死咱们啊。

花了点时间,好好复盘整理了一下:发现问题不在于产品经理,从业务上来看,产品提的这些需求都是需求合理的,而结果代码反复调整、测试反复测试,以及一些次要的功能致使注册接口不稳定,这些问题归根到底,主要仍是个人设计不合理致使的,将注册功能中的一些次要的功能耦合到注册的方法中了,而且这些功能可能会常常调整,致使了注册接口的不稳定性。

其实上面代码能够这么作:

找3我的:注册器、路人A、路人B。

注册器:负责将用户信息落库,落库成功以后,喊一声:用户XXX注册成功了。

路人A和路人B,竖起耳朵,当听到有人喊:XXX注册成功 的声音以后,当即行动作出下面反应:

路人A:负责给XXX发送一封注册邮件

路人B:负责给XXX发送优惠券

咱们来看一下:

注册器只负责将用户信息落库,及广播一条用户注册成功的消息。

A和B至关于一个监听者,只负责监听用户注册成功的消息,当听到有这个消息产生的时候,A和B就去作本身的事情。

这里面注册器是感知不到A/B存在的,A和B也不用感知注册器的存在,A/B只用关注是否有人广播:XXX注册成功了的消息,当AB听到有人广播注册成功的消息,他们才作出反应,其余时间闲着休息。

这种方式就很是好:

当不想给用户发送优惠券的时候,只须要将B去掉就好了,此时基本上也不用测试,注册一下B的代码就好了。

若注册成功以后须要更多业务,好比还须要给用户增长积分,只需新增一个监听者C,监听到注册成功消息后,负责给用户添加积分,此时根本不用去调整注册的代码,开发者和测试人员只须要确保监听者C中的正确性就能够了。

上面这种模式就是事件模式。

事件模式中的几个概念

事件源:事件的触发者,好比上面的注册器就是事件源。

事件:描述发生了什么事情的对象,好比上面的:xxx注册成功的事件

事件监听器:监听到事件发生的时候,作一些处理,好比上面的:路人A、路人B

下面咱们使用事件模式实现用户注册的业务

咱们先来定义和事件相关的几个类。

事件对象

表示全部事件的父类,内部有个source字段,表示事件源;咱们自定义的事件须要继承这个类。

package com.javacode2018.lesson003.demo1.test0.event;

/**
 * 事件对象
 */
public abstract class AbstractEvent {

    //事件源
    protected Object source;

    public AbstractEvent(Object source) {
        this.source = source;
    }

    public Object getSource() {
        return source;
    }

    public void setSource(Object source) {
        this.source = source;
    }
}

事件监听器

咱们使用一个接口来表示事件监听器,是个泛型接口,后面的类型E表示当前监听器须要监听的事件类型,此接口中只有一个方法,用来实现处理事件的业务;其定义的监听器须要实现这个接口。

package com.javacode2018.lesson003.demo1.test0.event;

/**
 * 事件监听器
 *
 * @param <E> 当前监听器感兴趣的事件类型
 */
public interface EventListener<E extends AbstractEvent> {
    /**
     * 此方法负责处理事件
     *
     * @param event 要响应的事件对象
     */
    void onEvent(E event);
}

事件广播器

  • 负责事件监听器的管理(注册监听器&移除监听器,将事件和监听器关联起来)

  • 负责事件的广播(将事件广播给全部的监听器,对该事件感兴趣的监听器会处理该事件)

package com.javacode2018.lesson003.demo1.test0.event;

/**
 * 事件广播器:
 * 1.负责事件监听器的管理(注册监听器&移除监听器,将事件和监听器关联起来)
 * 2.负责事件的广播(将事件广播给全部的监听器,对该事件感兴趣的监听器会处理该事件)
 */
public interface EventMulticaster {

    /**
     * 广播事件给全部的监听器,对该事件感兴趣的监听器会处理该事件
     *
     * @param event
     */
    void multicastEvent(AbstractEvent event);

    /**
     * 添加一个事件监听器(监听器中包含了监听器中可以处理的事件)
     *
     * @param listener 须要添加监听器
     */
    void addEventListener(EventListener<?> listener);


    /**
     * 将事件监听器移除
     *
     * @param listener 须要移除的监听器
     */
    void removeEventListener(EventListener<?> listener);
}

事件广播默认实现

package com.javacode2018.lesson003.demo1.test0.event;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 事件广播器简单实现
 */
public class SimpleEventMulticaster implements EventMulticaster {

    private Map<Class<?>, List<EventListener>> eventObjectEventListenerMap = new ConcurrentHashMap<>();

    @Override
    public void multicastEvent(AbstractEvent event) {
        List<EventListener> eventListeners = this.eventObjectEventListenerMap.get(event.getClass());
        if (eventListeners != null) {
            for (EventListener eventListener : eventListeners) {
                eventListener.onEvent(event);
            }
        }
    }

    @Override
    public void addEventListener(EventListener<?> listener) {
        Class<?> eventType = this.getEventType(listener);
        List<EventListener> eventListeners = this.eventObjectEventListenerMap.get(eventType);
        if (eventListeners == null) {
            eventListeners = new ArrayList<>();
            this.eventObjectEventListenerMap.put(eventType, eventListeners);
        }
        eventListeners.add(listener);
    }

    @Override
    public void removeEventListener(EventListener<?> listener) {
        Class<?> eventType = this.getEventType(listener);
        List<EventListener> eventListeners = this.eventObjectEventListenerMap.get(eventType);
        if (eventListeners != null) {
            eventListeners.remove(listener);
        }
    }

    /**
     * 获取事件监听器须要监听的事件类型
     *
     * @param listener
     * @return
     */
    protected Class<?> getEventType(EventListener listener) {
        ParameterizedType parameterizedType = (ParameterizedType) listener.getClass().getGenericInterfaces()[0];
        Type eventType = parameterizedType.getActualTypeArguments()[0];
        return (Class<?>) eventType;
    }

}

上面3个类支撑了整个时间模型,下面咱们使用上面三个类来实现注册的功能,目标是:高内聚低耦合,让注册逻辑方便扩展。

自定义用户注册成功事件类

继承了AbstractEvent

package com.javacode2018.lesson003.demo1.test0.userregister;

import com.javacode2018.lesson003.demo1.test0.event.AbstractEvent;

/**
 * 用户注册成功事件
 */
public class UserRegisterSuccessEvent extends AbstractEvent {
    //用户名
    private String userName;

    /**
     * 建立用户注册成功事件对象
     *
     * @param source   事件源
     * @param userName 当前注册的用户名
     */
    public UserRegisterSuccessEvent(Object source, String userName) {
        super(source);
        this.userName = userName;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}

用户注册服务

负责实现用户注册逻辑

package com.javacode2018.lesson003.demo1.test0.userregister;

import com.javacode2018.lesson003.demo1.test0.event.EventMulticaster;

/**
 * 用户注册服务
 */
public class UserRegisterService {
    //事件发布者
    private EventMulticaster eventMulticaster; //@0

    /**
     * 注册用户
     *
     * @param userName 用户名
     */
    public void registerUser(String userName) { //@1
        //用户注册(将用户信息入库等操做)
        System.out.println(String.format("用户【%s】注册成功", userName)); //@2
        //广播事件
        this.eventMulticaster.multicastEvent(new UserRegisterSuccessEvent(this, userName)); //@3
    }

    public EventMulticaster getEventMulticaster() {
        return eventMulticaster;
    }

    public void setEventMulticaster(EventMulticaster eventMulticaster) {
        this.eventMulticaster = eventMulticaster;
    }
}

@0:事件发布者

@1:registerUser这个方法负责用户注册,内部主要作了2个事情

@2:模拟将用户信息落库

@3:使用事件发布者eventPublisher发布用户注册成功的消息:

下面咱们使用spring来将上面的对象组装起来

package com.javacode2018.lesson003.demo1.test0.userregister;

import com.javacode2018.lesson003.demo1.test0.event.EventListener;
import com.javacode2018.lesson003.demo1.test0.event.EventMulticaster;
import com.javacode2018.lesson003.demo1.test0.event.SimpleEventMulticaster;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration
@ComponentScan
public class MainConfig0 {

    /**
     * 注册一个bean:事件发布者
     *
     * @param eventListeners
     * @return
     */
    @Bean
    @Autowired(required = false)
    public EventMulticaster eventMulticaster(List<EventListener> eventListeners) { //@1
        EventMulticaster eventPublisher = new SimpleEventMulticaster();
        if (eventListeners != null) {
            eventListeners.forEach(eventPublisher::addEventListener);
        }
        return eventPublisher;
    }

    /**
     * 注册一个bean:用户注册服务
     *
     * @param eventMulticaster
     * @return
     */
    @Bean
    public UserRegisterService userRegisterService(EventMulticaster eventMulticaster) { //@2
        UserRegisterService userRegisterService = new UserRegisterService();
        userRegisterService.setEventMulticaster(eventMulticaster);
        return userRegisterService;
    }
}

上面有2个方法,负责向spring容器中注册2个bean。

@1:向spring容器中注册了一个bean:事件发布者,方法传入了EventListener类型的List,这个地方会将容器中全部的事件监听器注入进来,丢到EventMulticaster中。

@2:向spring容器中注册了一个bean:用户注册服务

来个测试用例模拟用户注册

package com.javacode2018.lesson003.demo1;

import com.javacode2018.lesson003.demo1.test0.userregister.MainConfig0;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class EventTest {

    @Test
    public void test0() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig0.class);
        //获取用户注册服务
        com.javacode2018.lesson003.demo1.test0.userregister.UserRegisterService userRegisterService =
                context.getBean(com.javacode2018.lesson003.demo1.test0.userregister.UserRegisterService.class);
        //模拟用户注册
        userRegisterService.registerUser("路人甲Java");
    }

}

运行输出

用户【路人甲Java】注册成功

添加注册成功发送邮件功能

下面添加一个注册成功发送邮件的功能,只须要自定义一个监听用户注册成功事件的监听器就能够了,其余代码不须要任何改动,以下

package com.javacode2018.lesson003.demo1.test0.userregister;


import com.javacode2018.lesson003.demo1.test0.event.EventListener;
import org.springframework.stereotype.Component;

/**
 * 用户注册成功事件监听器->负责给用户发送邮件
 */
@Component
public class SendEmailOnUserRegisterSuccessListener implements EventListener<UserRegisterSuccessEvent> {
    @Override
    public void onEvent(UserRegisterSuccessEvent event) {
        System.out.println(
                String.format("给用户【%s】发送注册成功邮件!", event.getUserName()));
    }
}

上面这个类使用了@Component,会被自动扫描注册到spring容器。

再次运行测试用例输出

用户【路人甲Java】注册成功
给用户【路人甲Java】发送注册成功邮件!

小结

上面将注册的主要逻辑(用户信息落库)和次要的业务逻辑(发送邮件)经过事件的方式解耦了。次要的业务作成了可插拔的方式,好比不想发送邮件了,只须要将邮件监听器上面的@Component注释就能够了,很是方便扩展。

上面用到的和事件相关的几个类,都是咱们本身实现的,其实这些功能在spring中已经帮咱们实现好了,用起来更容易一些,下面带你们来体验一下。

Spring中实现事件模式

事件相关的几个类

Spring中事件相关的几个类须要先了解一下,下面来个表格,将spring中事件相关的类和咱们上面自定义的类作个对比,方便你们理解

这些类和咱们自定义的类中代码有点相似,有兴趣的能够去看一下源码,这里就不列出来了。

硬编码的方式使用spring事件3步骤

步骤1:定义事件

自定义事件,须要继承ApplicationEvent类,

步骤2:定义监听器

自定义事件监听器,须要实现ApplicationListener接口,这个接口有个方法onApplicationEvent须要实现,用来处理感兴趣的事件。

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

    /**
     * Handle an application event.
     * @param event the event to respond to
     */
    void onApplicationEvent(E event);

}

步骤3:建立事件广播器

建立事件广播器ApplicationEventMulticaster,这是个接口,你能够本身实现这个接口,也能够直接使用系统给咱们提供的SimpleApplicationEventMulticaster,以下:

ApplicationEventMulticaster applicationEventMulticaster = new SimpleApplicationEventMulticaster();

步骤4:向广播器中注册事件监听器

将事件监听器注册到广播器ApplicationEventMulticaster中,如:

ApplicationEventMulticaster applicationEventMulticaster = new SimpleApplicationEventMulticaster();
applicationEventMulticaster.addApplicationListener(new SendEmailOnOrderCreateListener());

步骤5:经过广播器发布事件

广播事件,调用ApplicationEventMulticaster#multicastEvent方法广播事件,此时广播器中对这个事件感兴趣的监听器会处理这个事件。

applicationEventMulticaster.multicastEvent(new OrderCreateEvent(applicationEventMulticaster, 1L));

下面咱们来个案例将这5个步骤串起来感觉一下。

案例

实现功能:电商中订单建立成功以后,给下单人发送一封邮件,发送邮件的功能放在监听器中实现。

下面上代码

来个事件类:订单建立成功事件

package com.javacode2018.lesson003.demo1.test1;

import org.springframework.context.ApplicationEvent;

/**
 * 订单建立事件
 */
public class OrderCreateEvent extends ApplicationEvent {
    //订单id
    private Long orderId;

    /**
     * @param source  事件源
     * @param orderId 订单id
     */
    public OrderCreateEvent(Object source, Long orderId) {
        super(source);
        this.orderId = orderId;
    }

    public Long getOrderId() {
        return orderId;
    }

    public void setOrderId(Long orderId) {
        this.orderId = orderId;
    }
}

来个监听器:负责监听订单成功事件,发送邮件

package com.javacode2018.lesson003.demo1.test1;

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * 订单建立成功给用户发送邮件
 */
@Component
public class SendEmailOnOrderCreateListener implements ApplicationListener<OrderCreateEvent> {
    @Override
    public void onApplicationEvent(OrderCreateEvent event) {
        System.out.println(String.format("订单【%d】建立成功,给下单人发送邮件通知!", event.getOrderId()));
    }
}

测试用例

@Test
public void test2() throws InterruptedException {
    //建立事件广播器
    ApplicationEventMulticaster applicationEventMulticaster = new SimpleApplicationEventMulticaster();
    //注册事件监听器
    applicationEventMulticaster.addApplicationListener(new SendEmailOnOrderCreateListener());
    //广播事件订单建立事件
    applicationEventMulticaster.multicastEvent(new OrderCreateEvent(applicationEventMulticaster, 1L));
}

运行输出

订单【1】建立成功,给下单人发送邮件通知!

ApplicationContext容器中事件的支持

上面演示了spring中事件的使用,那么平时咱们使用spring的时候就这么使用?

非也非也,上面只是我给你们演示了一下原理。

一般状况下,咱们会使用以ApplicationContext结尾的类做为spring的容器来启动应用,下面2个是比较常见的

AnnotationConfigApplicationContext
ClassPathXmlApplicationContext

来看一个类图

对这个图咱们来解释一下:

1.AnnotationConfigApplicationContext和ClassPathXmlApplicationContext都继承了AbstractApplicationContext
2.AbstractApplicationContext实现了ApplicationEventPublisher接口
3.AbstractApplicationContext内部有个ApplicationEventMulticaster类型的字段

上面第三条,说明了AbstractApplicationContext内部已经集成了事件广播器ApplicationEventMulticaster,说明AbstractApplicationContext内部是具体事件相关功能的,这些功能是经过其内部的ApplicationEventMulticaster来实现的,也就是说将事件的功能委托给了内部的ApplicationEventMulticaster来实现。

ApplicationEventPublisher接口

上面类图中多了一个新的接口ApplicationEventPublisher,来看一下源码

@FunctionalInterface
public interface ApplicationEventPublisher {

    default void publishEvent(ApplicationEvent event) {
        publishEvent((Object) event);
    }

    void publishEvent(Object event);

}

这个接口用来发布事件的,内部定义2个方法都是用来发布事件的。

spring中不是有个ApplicationEventMulticaster接口么,此处怎么又来了一个发布事件的接口?

这个接口的实现类中,好比AnnotationConfigApplicationContext内部将这2个方法委托给ApplicationEventMulticaster#multicastEvent进行处理了。

因此调用AbstractApplicationContext中的publishEvent方法,也实现广播事件的效果,不过使用AbstractApplicationContext也只能经过调用publishEvent方法来广播事件。

获取ApplicationEventPublisher对象

若是咱们想在普通的bean中获取ApplicationEventPublisher对象,须要实现ApplicationEventPublisherAware接口

public interface ApplicationEventPublisherAware extends Aware {
    void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher);
}

spring容器会自动经过上面的setApplicationEventPublisher方法将ApplicationEventPublisher注入进来,此时咱们就可使用这个来发布事件了。

Spring为了简化事件的使用,提供了2种使用方式

  1. 面相接口的方式

  2. 面相@EventListener注解的方式

面相接口的方式

案例

实现用户注册成功后发布事件,而后在监听器中发送邮件的功能。

用户注册事件

须要继承ApplicationEvent

package com.javacode2018.lesson003.demo1.test2;

import org.springframework.context.ApplicationEvent;

/**
 * 用户注册事件
 */
public class UserRegisterEvent extends ApplicationEvent {
    //用户名
    private String userName;

    public UserRegisterEvent(Object source, String userName) {
        super(source);
        this.userName = userName;
    }

    public String getUserName() {
        return userName;
    }
}

发送邮件监听器

需实现ApplicationListener接口

package com.javacode2018.lesson003.demo1.test2;

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * 用户注册成功发送邮件
 */
@Component
public class SendEmailListener implements ApplicationListener<UserRegisterEvent> {

    @Override
    public void onApplicationEvent(UserRegisterEvent event) {
        System.out.println(String.format("给用户【%s】发送注册成功邮件!", event.getUserName()));

    }
}

用户注册服务

内部提供用户注册的功能,并发布用户注册事件

package com.javacode2018.lesson003.demo1.test2;


import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;

/**
 * 用户注册服务
 */
@Component
public class UserRegisterService implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher applicationEventPublisher;

    /**
     * 负责用户注册及发布事件的功能
     *
     * @param userName 用户名
     */
    public void registerUser(String userName) {
        //用户注册(将用户信息入库等操做)
        System.out.println(String.format("用户【%s】注册成功", userName));
        //发布注册成功事件
        this.applicationEventPublisher.publishEvent(new UserRegisterEvent(this, userName));
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { //@1
        this.applicationEventPublisher = applicationEventPublisher;
    }
}

注意上面实现了ApplicationEventPublisherAware接口,spring容器会经过@1ApplicationEventPublisher注入进来,而后咱们就可使用这个来发布事件了。

来个spring配置类

package com.javacode2018.lesson003.demo1.test2;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class MainConfig2 {
}

上测试用例

@Test
public void test2() throws InterruptedException {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(MainConfig2.class);
    context.refresh();
    //获取用户注册服务
    com.javacode2018.lesson003.demo1.test2.UserRegisterService userRegisterService =
            context.getBean(com.javacode2018.lesson003.demo1.test2.UserRegisterService.class);
    //模拟用户注册
    userRegisterService.registerUser("路人甲Java");
}

运行输出

用户【路人甲Java】注册成功
给用户【路人甲Java】发送注册成功邮件!

原理

spring容器在建立bean的过程当中,会判断bean是否为ApplicationListener类型,进而会将其做为监听器注册到AbstractApplicationContext#applicationEventMulticaster中,这块的源码在下面这个方法中,有兴趣的能够看一下

org.springframework.context.support.ApplicationListenerDetector#postProcessAfterInitialization

小结

从上面这个案例中能够看出,事件类、监听器类都是经过基于spring中的事件相关的一些接口来实现事件的功能,这种方式咱们就称做面相接口的方式。

面相@EventListener注解方式

用法

上面是经过接口的方式建立一个监听器,spring还提供了经过@EventListener注解的方式来建立一个监听器,直接将这个注解标注在一个bean的方法上,那么这个方法就能够用来处理感兴趣的事件,使用更简单,以下,方法参数类型为事件的类型:

@Component
public class UserRegisterListener {
    @EventListener
    public void sendMail(UserRegisterEvent event) {
        System.out.println(String.format("给用户【%s】发送注册成功邮件!", event.getUserName()));
    }
}

案例

注册成功以后:来2个监听器:一个负责发送邮件、一个负责发送优惠券。

其余代码都不上了,和上面案例中的同样,主要看监听器的代码,以下:

package com.javacode2018.lesson003.demo1.test3;

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

/**
 * 用户注册监听器
 */
@Component
public class UserRegisterListener {
    @EventListener
    public void sendMail(UserRegisterEvent event) {
        System.out.println(String.format("给用户【%s】发送注册成功邮件!", event.getUserName()));
    }

    @EventListener
    public void sendCompon(UserRegisterEvent event) {
        System.out.println(String.format("给用户【%s】发送优惠券!", event.getUserName()));
    }
}

这块案例代码

com.javacode2018.lesson003.demo1.EventTest#test3

运行结果

用户【路人甲Java】注册成功
给用户【路人甲Java】发送优惠券!
给用户【路人甲Java】发送注册成功邮件!

原理

spring中处理@EventListener注解源码位于下面的方法中

org.springframework.context.event.EventListenerMethodProcessor#afterSingletonsInstantiated

EventListenerMethodProcessor实现了SmartInitializingSingleton接口,SmartInitializingSingleton接口中的afterSingletonsInstantiated方法会在全部单例的bean建立完成以后被spring容器调用,这块的内容能够去看一下:Bean生命周期详解

idea对注解的方式支持比较好

注解的方式实现监听器,idea对这块支持比较好,时间发布的地方会显示一个耳机,点击这个耳机的时候,spring会帮咱们列出这个事件有哪些监听器

点击耳机列出了2个监听器,能够快速定位到监听器,以下

一样监听器的地方也有一个广播的图标,以下图

点击上面这个广播的图标,能够快速导航到事件发布的地方,至关方便。

监听器支持排序功能

若是某个事件有多个监听器,默认状况下,监听器执行顺序是无序的,不过咱们能够为监听器指定顺序。

经过接口实现监听器的状况

若是自定义的监听器是经过ApplicationListener接口实现的,那么指定监听器的顺序有三种方式

方式1:实现org.springframework.core.Ordered接口

须要实现一个getOrder方法,返回顺序值,值越小,顺序越高

int getOrder();

方式2:实现org.springframework.core.PriorityOrdered接口

PriorityOrdered接口继承了方式一中的Ordered接口,因此若是你实现PriorityOrdered接口,也须要实现getOrder方法。

方式3:类上使用@org.springframework.core.annotation.Order注解

看一下这个注解的源码

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {

    int value() default Ordered.LOWEST_PRECEDENCE;

}

value属性用来指定顺序

这几种方式排序规则

PriorityOrdered#getOrder ASC,Ordered或@Order ASC

经过@EventListener实现事件监听器的状况

能够在标注@EventListener的方法上面使用@Order(顺序值)注解来标注顺序,如:

@EventListener
@Order(1)
public void sendMail(com.javacode2018.lesson003.demo1.test3.UserRegisterEvent event) {
    System.out.println(String.format("给用户【%s】发送注册成功邮件!", event.getUserName()));
}

案例

package com.javacode2018.lesson003.demo1.test4;

import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 用户注册监听器
 */
@Component
public class UserRegisterListener {
    @EventListener
    @Order(1)
    public void sendMail(UserRegisterEvent event) {
        System.out.println(String.format("【%s】,给用户【%s】发送注册成功邮件!", Thread.currentThread(), event.getUserName()));
    }

    @EventListener
    @Order(0)
    public void sendCompon(UserRegisterEvent event) {
        System.out.println(String.format("【%s】,给用户【%s】发送优惠券!", Thread.currentThread(), event.getUserName()));
    }
}

上面会先发送优惠券、而后再发送邮件。

上面输出中顺便将线程信息也输出了。

对应测试用例

com.javacode2018.lesson003.demo1.EventTest#test4

运行输出

【Thread[main,5,main]】,用户【路人甲Java】注册成功
【Thread[main,5,main]】,给用户【路人甲Java】发送优惠券!
【Thread[main,5,main]】,给用户【路人甲Java】发送注册成功邮件!

从输出中能够看出上面程序的执行都在主线程中执行的,说明监听器中的逻辑和注册逻辑在一个线程中执行的,此时若是监听器中的逻辑比较耗时或者失败,直接会致使注册失败,一般咱们将一些非主要逻辑能够放在监听器中执行,至于这些非主要逻辑成功或者失败,最好不要对主要的逻辑产生影响,因此咱们最好能将监听器的运行和主业务隔离开,放在不一样的线程中执行,主业务不用关注监听器的结果,spring中支持这种功能,下面继续看。

监听器异步模式

先来看看到底如何实现?

监听器最终是经过ApplicationEventMulticaster内部的实现来调用的,因此咱们关注的重点就是这个类,这个类默认有个实现类SimpleApplicationEventMulticaster,这个类是支持监听器异步调用的,里面有个字段:

private Executor taskExecutor;

高并发比较熟悉的朋友对Executor这个接口是比较熟悉的,能够用来异步执行一些任务。

咱们经常使用的线程池类java.util.concurrent.ThreadPoolExecutor就实现了Executor接口。

再来看一下SimpleApplicationEventMulticaster中事件监听器的调用,最终会执行下面这个方法

@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) { //@1
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            invokeListener(listener, event);
        }
    }
}

上面的invokeListener方法内部就是调用监听器,从代码@1能够看出,若是当前executor不为空,监听器就会被异步调用,因此若是须要异步只须要让executor不为空就能够了,可是默认状况下executor是空的,此时须要咱们来给其设置一个值,下面咱们须要看容器中是如何建立广播器的,咱们在那个地方去干预。

一般咱们使用的容器是AbstractApplicationContext类型的,须要看一下AbstractApplicationContext中广播器是怎么初始化的,就是下面这个方法,容器启动的时候会被调用,用来初始化AbstractApplicationContext中的事件广播器applicationEventMulticaster

public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";

protected void initApplicationEventMulticaster() {
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
        this.applicationEventMulticaster =
            beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
    }
    else {
        this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
        beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
    }
}

上面逻辑解释一下:判断spring容器中是否有名称为applicationEventMulticaster的bean,若是有就将其做为事件广播器,不然建立一个SimpleApplicationEventMulticaster做为广播器,并将其注册到spring容器中。

从上面能够得出结论:咱们只须要自定义一个类型为SimpleApplicationEventMulticaster名称为applicationEventMulticaster的bean就能够了,顺便给executor设置一个值,就能够实现监听器异步执行了。

具体实现以下

package com.javacode2018.lesson003.demo1.test5;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.scheduling.concurrent.ThreadPoolExecutorFactoryBean;

import java.util.concurrent.Executor;

@ComponentScan
@Configuration
public class MainConfig5 {
    @Bean
    public ApplicationEventMulticaster applicationEventMulticaster() { //@1
        //建立一个事件广播器
        SimpleApplicationEventMulticaster result = new SimpleApplicationEventMulticaster();
        //给广播器提供一个线程池,经过这个线程池来调用事件监听器
        Executor executor = this.applicationEventMulticasterThreadPool().getObject();
        //设置异步执行器
        result.setTaskExecutor(executor);//@1
        return result;
    }

    @Bean
    public ThreadPoolExecutorFactoryBean applicationEventMulticasterThreadPool() {
        ThreadPoolExecutorFactoryBean result = new ThreadPoolExecutorFactoryBean();
        result.setThreadNamePrefix("applicationEventMulticasterThreadPool-");
        result.setCorePoolSize(5);
        return result;
    }
}

@1:定义了一个名称为applicationEventMulticaster的事件广播器,内部设置了一个线程池用来异步调用监听器

这段代码对应的测试用例

com.javacode2018.lesson003.demo1.EventTest#test5

运行输出

当前线程【Thread[main,5,main]】,用户【路人甲Java】注册成功
当前线程【Thread[applicationEventMulticasterThreadPool-2,5,main]】,给用户【路人甲Java】发送注册成功邮件!
当前线程【Thread[applicationEventMulticasterThreadPool-1,5,main]】,给用户【路人甲Java】发放一些优惠券!

此时实现了监听器异步执行的效果。

关于事件使用建议

  1. spring中事件是使用接口的方式仍是使用注解的方式?具体使用哪一种方式均可以,不过在公司内部最好你们都统一使用一种方式

  2. 异步事件的模式,一般将一些非主要的业务放在监听器中执行,由于监听器中存在失败的风险,因此使用的时候须要注意。若是只是为了解耦,可是被解耦的次要业务也是必需要成功的,可使用消息中间件的方式来解决这些问题。

  3. 事件的使用就到这里,有问题的欢迎留言讨论。

案例源码

https://gitee.com/javacode2018/spring-series

路人甲java全部案例代码之后都会放到这个上面,你们watch一下,能够持续关注动态。

Spring系列

  1. Spring系列第1篇:为什么要学spring?

  2. Spring系列第2篇:控制反转(IoC)与依赖注入(DI)

  3. Spring系列第3篇:Spring容器基本使用及原理

  4. Spring系列第4篇:xml中bean定义详解(-)

  5. Spring系列第5篇:建立bean实例这些方式大家都知道?

  6. Spring系列第6篇:玩转bean scope,避免跳坑里!

  7. Spring系列第7篇:依赖注入之手动注入

  8. Spring系列第8篇:自动注入(autowire)详解,高手在于坚持

  9. Spring系列第9篇:depend-on究竟是干什么的?

  10. Spring系列第10篇:primary能够解决什么问题?

  11. Spring系列第11篇:bean中的autowire-candidate又是干什么的?

  12. Spring系列第12篇:lazy-init:bean延迟初始化

  13. Spring系列第13篇:使用继承简化bean配置(abstract & parent)

  14. Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?

  15. Spring系列第15篇:代理详解(Java动态代理&cglib代理)?

  16. Spring系列第16篇:深刻理解java注解及spring对注解的加强(预备知识)

  17. Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)

  18. Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)

  19. Spring系列第18篇:@import详解(bean批量注册)

  20. Spring系列第20篇:@Conditional经过条件来控制bean的注册

  21. Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)

  22. Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解

  23. Spring系列第23篇:Bean生命周期详解

  24. Spring系列第24篇:父子容器详解

  25. Spring系列第25篇:@Value【用法、数据来源、动态刷新】

  26. Spring系列第26篇:国际化详解

更多好文章

  1. Java高并发系列(共34篇)

  2. MySql高手系列(共27篇)

  3. Maven高手系列(共10篇)

  4. Mybatis系列(共12篇)

  5. 聊聊db和缓存一致性常见的实现方式

  6. 接口幂等性这么重要,它是什么?怎么实现?

  7. 泛型,有点难度,会让不少人懵逼,那是由于你没有看这篇文章!

世界上最好的关系是相互成就,点赞转发 感恩开心????

路人甲java

▲长按图片识别二维码关注

路人甲Java:工做10年的前阿里P7,全部文章以系列的方式呈现,带领你们成为java高手,目前已出:java高并发系列、mysql高手系列、Maven高手系列、mybatis系列、spring系列,正在连载springcloud系列,欢迎关注!