spring 事件处理模型/观察者

spring 事件处理模型/观察者


Spring的事件(Application Event)为Bean与Bean之间的消息通信提供了支持,当一个Bean处理完一个任务之后,希望另外一个Bean知道并且能做及时的处理,这时我们需要让另外一个bean监听当前Bean所发送的事件。个人认为主要优点是降低耦合。Spring事件需要遵循如下流程

  • 自定义事件,继承ApplicationEvent
  • 自定义事件监听器,实现ApplicationListener
  • 使用容器发布事件
  • 消息对列

  简单理解就是为系统业务逻辑之间进行了解耦,事件发布者并不需要考虑谁去监听,监听具体的实现内容是什么,发布者的工作只是为了发布事件而已。

例如:用户注册的例子:
用户注册成功后,需要做很多事情:

  1. 增加积分
  2. 发送确认邮件
  3. 如果式游戏用户,还需要赠送游戏大礼包
  4. 建立索引用户数据

  5.   解决的方法是,增加了一个Listener来解耦UserService和其他服务,即注册成功后,只需要通知相关的监听器,不需要关系它们如何处理。增删功能非常容易。

一. 自定义事件

@Data
public class DemoEvent extends ApplicationEvent {
    private String text;

    /**
     * Create a new ApplicationEvent.
     *
     * @param source 发生事件的对象
     */
    public DemoEvent(Object source, String text) {
        super(source);
        this.text = text;
    }
}

  定义了一个事件DemoEvent继承了ApplicationEvent,继承后必须重载构造函数,构造函数的参数可以任意指定,其中source参数指的是发生事件的对象,一般我们在发布事件时使用的是this关键字代替本类对象。

二.事件监听

在Spring内部中有多种方式实现监听如:

  1. @EventListener注解
  2. 实现ApplicationListener泛型接口
  3. 实现SmartApplicationListener接口,有序监听器

2.1 @EventListener注解

    /**
     * 注册监听实现方法
     * @param DemoEvent 用户注册事件
     */
    @EventListener
    public void register(DemoEvent demoEvent)
    {
        //获取信息
        String  text = demoEvent.getText();

        //../省略逻辑
    }

  监听类被Spring管理即可,注册监听实现方法上添加@EventListener注解,该注解会根据方法内配置的事件完成监听。使用注解式,可以监听统一事件的不同实现,只需要再方法上加注解,节省代码。

2.2 实现ApplicationListener泛型接口

Component
public class DemoEventListener implements ApplicationListener<DemoEventListener>
{
    /**
     * 实现监听
     * @param userRegisterEvent
     */
    @Override
    public void onApplicationEvent(DemoEvent demoEvent) {
        //获取注册用户对象
        String text = demoEvent.getText();
        //../省略逻辑
    }
}

ApplicationListener 源码如下:

package org.springframework.context;

import java.util.EventListener;

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    void onApplicationEvent(E var1);
}

  使用@Component注解来声明该监听需要被Spring注入管理,当有DemoEvent事件发布时监听程序会自动调用onApplicationEvent方法并且将DemoEvent对象作为参数传递。这样的监听器是无序的

2.3 定义有序的监听器

方法一: @EventListener + @Order
方法二:实现SmartApplicationListener接口

package org.springframework.context.event;

public interface SmartApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {

    // 如果实现支持该事件类型 那么返回true
    boolean supportsEventType(Class<? extends ApplicationEvent> var1);

    default boolean supportsSourceType(@Nullable Class<?> sourceType) {
        return true;
    }

    //顺序,即监听器执行的顺序,值越小优先级越高
    default int getOrder() {
        return 2147483647;
    }

示例一:

@Component
public class syncKeywordDeleteListener implements SmartApplicationListener {
    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> aClass) {
        return KeywordDeleteEvent.class == aClass;
    }

    @Override
    public int getOrder() {
        return 1;
    }

    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
       //逻辑代码
    System.out.println("hello");
    }
}
//-------------------
@Component
public class DeleteKeywordsLitener implements SmartApplicationListener {
    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> aClass) {
        return KeywordDeleteEvent.class == aClass;
    }

    //执行顺序二
    @Override
    public int getOrder() {
        return 2;
    }

    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        System.out.println("hello,顺序二");
    }

示例二:

@Component
public class KeywordDeleteEventListener {

    /**
     * @param event event
     */
    @EventListener
    @Order(1)
    public void syncKeywordDelete(KeywordDeleteEvent event) throws NoSuchMethodException {
        String name = Thread.currentThread().getStackTrace()[1].getMethodName();
        Method method = this.getClass().getDeclaredMethod(name, KeywordDeleteEvent.class);
        Integer order = method.getAnnotation(Order.class).value();
        if (event.getOrder() > order) {
            log.debug("当前step已经执行过,不再执行");
            return;
        }
        //逻辑代码
        System.out.println("hello");
    }
    //-------------------------------------------------------------------
    @EventListener
    @Order(2)
    public void DeleteKeywords(KeywordDeleteEvent event) throws NoSuchMethodException {
        String name = Thread.currentThread().getStackTrace()[1].getMethodName();
        Method method = this.getClass().getDeclaredMethod(name, KeywordDeleteEvent.class);
        Integer order = method.getAnnotation(Order.class).value();
        if (event.getOrder() > order) {
            log.debug("当前step已经执行过,不再执行");
            return;
        }
         System.out.println("hello,顺序二");
    }

}

巧妙的利用了@Order注解
@Order,可以定义在类上,也可以定义到方法上,指定一个先后顺序。

三. 发布事务

    @Resource
    private ApplicationContext applicationContext;
    KeywordDeleteEvent event = new KeywordDeleteEvent(this);
    applicationContext.publishEvent(event);

在这里插入图片描述

  ApplicationContext接口继承了ApplicationEventPublisher,并在AbstractApplicationContext实现了具体代码如下,实际执行是委托给ApplicationEventMulticaster(可以认为是多播):

 protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
        //省略部分
		getApplicationEventMulticaster().multicastEvent(event);
		if (this.parent != null) {
			this.parent.publishEvent(event);
		}
    }

  Spring事件机制是观察者模式的一种实现,但是除了发布者和监听者者两个角色之外,还有一个EventMultiCaster的角色负责把事件转发给监听者如下;
在这里插入图片描述

applicationContext.publishEvent(event); 是会将事件发送给了EventMultiCaster,EventMultiCaster注册着所有的Listener,然后根据事件类型决定转发给那个Listener。
TODO EventMultiCaster,源码学习

四. 实现异步监听

  @Aysnc其实是Spring内的一个组件,可以实现方法实现异步调用,节省耗时。内部实现机制是线程池任务ThreadPoolTaskExecutor,被注解的方法调用的时候,会在新的线程中执行,而调用它的方法会在原来的线程中执行。

4.1 配置线程池,开启异步处理

@Configuration
@EnableAsync
public class TaskExecutorConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(5);//线程池大小
        taskExecutor.setMaxPoolSize(10);//线程池最大线程数
        taskExecutor.setQueueCapacity(25);//最大等待任务数
        taskExecutor.initialize();
        return taskExecutor;
    }
}

@EnableAsync注解开启支持异步处理
异步监听,本质上还是Spring的异步任务,只是在监听事件上加了异步

    @EventListener
    @Async
    @Order(3)
    public void upodate(KeywordDeleteEvent event) throws NoSuchMethodException {
        //异步r任务
        String name = Thread.currentThread().getStackTrace()[1].getMethodName();
        Method method = this.getClass().getDeclaredMethod(name, UpdateCampaignDateEvent.class);
        Integer order = method.getAnnotation(Order.class).value();
        if (event.getOrder() > order) {
            log.debug("当前step已经执行过,不再执行");
            return;
        }
        //具体逻辑
    }
}

五 消息队列的联想

  上面的这种形式让我联想到消息队列。消息队列有两种类型,点对点消息队列模型和发布订阅消息模型。

  • 发布订阅模型
    支持向特定的消息主题发布消息,0个或者1个订阅者可以接收特定的topic的消息。在这种模型下,发布者和订阅者彼此不知道对方。这种模型如下图:

在这里插入图片描述

  多个消费者可以获得消息在发布者和订阅者之间存在时间依赖性,即必须先订阅,再发送消息,而后接收订阅的消息,这个操作顺序必须保证。

  • 点对点消息队列模型

在这里插入图片描述

每一个成功处理的消息都由消息消费者签收确认(Acknowledge)

此处只是简单的说明自己的联想,详细的消息队列还会有文章介绍,敬请期待


六 Java的事件(对比Spring,原理一样)

Java事件主要角色

  • 事件源(Source):规定事件由谁来产生,在listener中作路由
  • 事件对象(EventObject):传递信息的作用,对source的包装,根据不同的构造方法,可以附带除source之外的其他信息(参考上面)
  • 事件监听(EventListener):业务逻辑处,根据事件中的事件源(source)或者事件对象,进行不同的路由,实现不同的业务。

source+event -> 触发 Listener

参考:
消息队列技术之基本概念

[

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值