Spring进阶(二十)之事件处理

目录

为什么需要使用事件这种模式

事件模式中的几个概念

使用事件模式实现上面用户注册的业务

事件对象

事件监听器

事件广播器

事件广播默认实现

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

用户注册服务

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

测试用例模拟用户注册 

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

小结

Spring中实现事件模式

事件相关的几个类

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

案例

小结

面向接口的方式

面向@EventListener注解方式

监听器支持排序功能

通过接口实现监听器的情况

通过@EventListener注解实现监听器的情况

监听器异步模式

事件使用的建议


为什么需要使用事件这种模式

先来看一个业务场景:

产品经理:路人,这两天你帮我实现一个注册的功能 我:注册功能比较简单,将用户信息入库就可以了,伪代码如下:

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字段,表示事件源;我们自定义的事件需要继承这个类。

public abstract class AbstractEvent {

    private Object source;

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

    public Object getSource() {
        return source;
    }

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

事件监听器

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

public interface EventListener {

    void onEvent(E e);
}

事件广播器

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

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

public interface EventMulticaster {

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

    /**
     * 添加一个事件监听器(监听器中包含了监听器中能够处理的事件)
     *
     * @param listener 需要添加监听器
     */
    void addEventListener(EventListener listener);

    /**
    * 将事件监听器移除
    *
    * @param listener 需要移除的监听器
    */

    void removeEventListener(EventListener listener);
}

事件广播默认实现

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

    private Map, List> eventObjectEventListenerMap = new HashMap<>();


    @Override
    public void multicastEvent(AbstractEvent event) {

        List 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 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 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个类支撑了整个事件模型,下面我们使用上面三个类来实现注册的功能,目标是:高内聚低耦合, 让注册逻辑方便扩展。

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

/**
 * 用户注册成功事件
 */
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;
    }
}

用户注册服务


/**
 * 用户注册服务
 */
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来将上面的对象组装起来

@Configuration
@ComponentScan
public class MainConfig {
    /**
     * 注册一个bean:事件发布者
     *
     * @param eventListeners
     * @return
     */
    @Bean
    @Autowired(required = false)
    public EventMulticaster eventMulticaster(List 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: 用户注册服务

测试用例模拟用户注册 

public class EventTest {

    @Test
    public void test0() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);

        //获取用户注册服务
        UserRegisterService userRegisterService = context.getBean(UserRegisterService.class);

        //模拟用户注册
        userRegisterService.registerUser("测试用户1");
    }
}

运行输出:

用户【测试用户1】注册成功

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

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

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

运行输出:

用户【测试用户1】注册成功
给用户【测试用户1】发送注册成功邮件!

小结

上面将注册的主要逻辑(用户信息落库)和次要的业务逻辑(发送邮件)通过事件的方式解耦了。次要的业务做成了可插拔的方式,比如不想发送邮件了,只需要将邮件监听器上面的 @Component 注释就可 以了,非常方便扩展。 上面用到的和事件相关的几个类,都是我们自己实现的,其实这些功能在spring中已经帮我们实现好 了,用起来更容易一些,下面带大家来体验一下。

Spring中实现事件模式

事件相关的几个类

Spring中事件相关的几个类需要先了解一下,下面来个表格,将spring中事件相关的类和我们上面自定 义的类做个对比,方便大家理解

Spring进阶(二十)之事件处理_第1张图片

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

步骤1:定义事件

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

步骤2:定义监听器

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

@FunctionalInterface
public interface ApplicationListener 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));

案例

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

事件类:订单创建成功事件


/**
 * 订单创建事件
 */
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;
    }
}

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

/**
 * 订单创建成功给用户发送邮件
 */
@Component
public class SendEmailOnOrderCreateListener implements ApplicationListener {
    @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】创建成功,给下单人发送邮件通知!

小结

用上面spring集成好的事件处理的方式,简化了不少。但是,我们还可以进一步简化,另外两种方式来实现,面向接口的方式和面向@EventListener注解的方式

面向接口的方式

来一个案例:实现用户注册成功后发布事件,然后在监听器中发送邮件的功能,更好的展现怎么使用。

用户注册事件

需要继承 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 接口

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

用户注册服务

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

/**
 * 用户注册服务
 */
@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;
    }

}

测试

    @Test
    public void test3() throws InterruptedException {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(MainConfig1.class);
        context.refresh();

        //获取用户注册服务
        com.example.Way1.UserRegisterService userRegisterService = context.getBean(com.example.Way1.UserRegisterService.class);
        //模拟用户注册
        userRegisterService.registerUser("test");
    }

运行输出

用户【test】注册成功
给用户【test】发送注册成功邮件!

面向@EventListener注解方式

其实和上面的面向接口的差不多,唯一的区别就是监听器不用实现ApplicationListener接口了,而是用@EventListener注解来代替,其它部分都一样。


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

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

    public String getUserName() {
        return userName;
    }
}

/**
 * 用户注册监听器
 */
@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()));
    }
}


/**
 * 用户注册服务
 */
@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;
    }

}

测试:

    @Test
    public void test4() throws InterruptedException {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(MainConfig2.class);
        context.refresh();

        //获取用户注册服务
        com.example.Way2.UserRegisterService userRegisterService = context.getBean(com.example.Way2.UserRegisterService.class);
        //模拟用户注册
        userRegisterService.registerUser("test");
    }

用户【test】注册成功
给用户【test】发送注册成功邮件!
给用户【test】发送优惠券!

监听器支持排序功能

如果某个事件有多个监听器,默认情况下,监听器执行顺序是无序的,不过我们可以为监听器指定顺序。

通过接口实现监听器的情况

指定监听器的顺序有三种方式

方式一:

实现org.springframework.core.Ordered接口,需要实现一个getOrder方法,返回顺序值,值越小,顺序越高。

方式二:

实现org.springframework.core.PriorityOrdered接口

PriorityOrdered接口继承了方式一中的Ordered接口,所以如果你实现PriorityOrdered接口,也需要实 现getOrder方法。

方式三:

类上使用@org.springframework.core.annotation.Order注解

通过@EventListener注解实现监听器的情况

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

监听器异步模式

具体实现如下:
 

@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 的事件广播器,内部设置了一个线程池 用来异步调用监听器

事件使用的建议

  1. spring中事件是使用接口的方式还是使用注解的方式,具体使用哪种方式都可以,不过在公司内部 最好大家都统一使用一种方式
  2. 异步事件的模式,通常将一些非主要的业务放在监听器中执行,因为监听器中存在失败的风险,所 以使用的时候需要注意。如果只是为了解耦,但是被解耦的次要业务也是必须要成功的,可以使用消息中间件的方式来解决这些问题。

你可能感兴趣的:(spring,java,开发语言,spring)