【啃啊啃 Spring5 源码】细碎一:spring 事件机制

阅读spring源码时,看到ApplicationEvent相关的代码觉得熟悉又困惑,深入了解了一下,发现原来是spring事件机制(原谅我之前没用过……)。
这里在【Spring4揭秘 基础1】监听器和事件的基础下进行一下扩展深入,感谢这篇博文的作者,他的spring基础系列文章让我在阅读源码时,轻松了不少。

注:源码部分根据spring-5.0.7版本分析

设计模式

spring事件机制其实就是观察者模式的一种体现。忘记或不熟悉观察者模式的朋友可以看我前面的总结:Head First 设计模式(二)观察者模式

观察者模式简单可分为两部分:主题和观察者。当一个主题改变状态时,它的所有依赖者都会收到通知并进行自动更新。

Spring事件机制简单可分为三部分:事件、广播、观察者。 “主题改变状态” 这个动作被抽离成了 一个“事件”,由一个持有所有观察者的“广播容器” 进行广播,“观察者”们 接收到相应事件后进行自动更新。

这种设计其实继承自Java本身的事件机制:

  1. java.util.EventObject
    事件状态对象的基类,它封装了事件源对象以及和事件相关的信息。所有java的事件类都需要继承该类。
  2. java.util.EventListener
    观察者基类,当事件源的属性或状态改变的时候,调用相应观察者内的回调方法。
  3. Source
    主题类,java中未定义,持有所有的观察者,当主题状态发生改变,产生事件,负责向所有观察者发布事件

Java的事件机制这里不敞开讲,想了解可看:java 事件机制

Spring中的事件机制

Spring的事件机制相关的核心类有四个:

  • ApplicationEvent: Spring中的事件基类,继承自java.util.EventObject,创建是需要指定事件源
public abstract class ApplicationEvent extends EventObject {
    /**
     * 创建一个事件,需要指定事件源
     */
    public ApplicationEvent(Object source) {
        super(source);
        this.timestamp = System.currentTimeMillis();
    }
}
  • ApplicationEventPublisher:发布事件者,调用广播发布事件
public interface ApplicationEventPublisher {
    /**发布事件*/
    default void publishEvent(ApplicationEvent event) {
        publishEvent((Object) event);
    }
    void publishEvent(Object event);
}
  • ApplicationEventMulticaster:广播,持有观察者集合,可向集合内的所有观察者通知事件
public interface ApplicationEventMulticaster {
    /**
     * 添加监听者(观察者)
     */
    void addApplicationListener(ApplicationListener<?> listener);

    /**
     * 删除监听者(观察者)
     */
    void removeApplicationListener(ApplicationListener<?> listener);

    /**
     * 向所有监听者发布事件
     */
    void multicastEvent(ApplicationEvent event);
}
  • ApplicationListener:观察者,接收对应事件后,执行逻辑
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    /**
     * 接收事件后,执行相应逻辑
     */
    void onApplicationEvent(E event);
}

事件发布者ApplicationEventPublisher持有广播,而广播ApplicationEventMulticaster持有若干观察者ApplicationListener。一个事件ApplicationEvent可以通过发布者ApplicationEventPublisher发布后,会调用广播ApplicationEventMulticaster通知所有观察者,观察者ApplicationListener收到通知后执行相关操作。

下面举例说明:
当一个用户注册结束后,我们想要将这个事件发生给短信监听者和邮件监听者,让他们向用户发送短信和邮件。

public class EventDemo {

    public static void main(String[] args) {
        //构建广播器
        ApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
        //广播添加监听器
        multicaster.addApplicationListener(new RegisterListener1());
        multicaster.addApplicationListener(new RegisterListener2());

        //构建事件发布者
        MyEventPublicsher eventPublicsher = new MyEventPublicsher();
        //事件发布者增加广播
        eventPublicsher.setEventMulticaster(multicaster);

        //构建注册事件
        User user = new User("jack", "18782252509", "jack_email@163.com");
        System.out.println("用户注册……");
        RegisterEvent registerEvent = new RegisterEvent(user);

        //发布注册事件
        eventPublicsher.publishEvent(registerEvent);
    }

    /**
     * 用户实体类
     */
    public static class User{
        private String id;
        private String name;
        private String phone;
        private String email;

        public User(String name, String phone, String email) {
            this.name = name;
            this.phone = phone;
            this.email = email;
        }

        //.....GET AND SET

    }
    /**
     * 自定义注册事件
     */
    public static class RegisterEvent extends ApplicationEvent {
        //事件的构造方法中,必须制定事件源
        public RegisterEvent(User user) {
            super(user);
        }

        public User getUser(){
            return (User) getSource();
        }
    }

    /**
     * 注册事件监听者1-短信监听者(即观察者),负责注册后发生短信
     * 注意:实现接口时,在泛形中指定事件类型,则只监听该类型事件。若不指定,则默认监听所有事件。
     */
    public static class RegisterListener1 implements ApplicationListener<RegisterEvent> {
        public void onApplicationEvent(RegisterEvent event) {
            User user = event.getUser();
            System.out.println("用户:"+ user.getName()+"注册结束,向手机"+user.getPhone()+"发送短信!");
        }
    }

    /**
     * 注册事件监听者2-邮件监听者(即观察者),负责注册后发送邮件
     * 注意:实现接口时,在泛形中指定事件类型,则只监听该类型事件。若不指定,则默认监听所有事件。
     */
    public static class RegisterListener2 implements ApplicationListener<RegisterEvent> {

        public void onApplicationEvent(RegisterEvent event) {
            User user = event.getUser();
            System.out.println("用户:"+ user.getName()+"注册结束,发生邮件:"+user.getEmail());
        }
    }


    /**
     * 事件发布者,持有监听者
     */
    public static class MyEventPublicsher implements  ApplicationEventPublisher{
        //广播
        private ApplicationEventMulticaster eventMulticaster;

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

        //发布事件
        public void publishEvent(Object event) {
            eventMulticaster.multicastEvent((ApplicationEvent) event);
        }
    }

}

输出:

用户注册后
用户:jack注册结束,向手机18782252509发送短信!
用户:jack注册结束,发生邮件:jack_email@163.com

源码细节解析

我们主要分析下广播的细节,以SimpleApplicationEventMulticaster为例:

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
        @Override
        public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
            //事件被统一封装成了ResolvableType,方便形参入口统一
            ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
            //根据事件类型,通过泛形反射获取对应的监听者
            for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
                //获取广播配置的线程池
                Executor executor = getTaskExecutor();
                //如果有配置线程池,则异步通知事件监听者
                if (executor != null) {
                    executor.execute(() -> invokeListener(listener, event));
                }
                //没有配置线程池,同步通知事件监听者
                else {
                    invokeListener(listener, event);
                }
            }
        }

        private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
                try {
                    //执行监听者对应逻辑
                    listener.onApplicationEvent(event);
                }
                catch (ClassCastException ex) {
                    ……
                }
            }
}

我们可以看到spring广播时,会先去判断有没有配置线程池,如果配置则使用线程池异步执行监听者逻辑,否则同步。

需要注意的是,我们使用spring事件机制时,默认是没有配置线程池的,也就是默认所有的通知都是同步的,需要手动指定线程池才会开启同步。

应用

设计一个业务场景:当一个用户完成贷款订单后,我们希望执行发送提醒短信、调用积分服务增加积分、通知风控服务重算风控值(后续操作可能增加)等功能。这种业务需求开始很可能写成同步代码。

//创建订单
public void createOrder(Order order){
    创建贷款订单;
    发送提醒短信;
    调用积分服务增加积分;
    调用风控服务推送订单信息;
    ……
    返回;
}

随着业务复杂度的增加,我们很快发现createOrder()创建订单这个方法耦合了太多与注册无关的逻辑,即影响了原本创建订单方法的效率,在设计上又不符合“开闭原则”。

现在使用spring事件机制我们来解耦,将与注册无关的操作改为异步。这里直接使用注解式写法。

  • 首先我们修改spring中的广播,为它注入我们自定义的线程池,在spring配置加上:
    <!--自定义线程池-->
    <bean id="myExecutor" class="org.springframework.core.task.SimpleAsyncTaskExecutor" />

    <!--修改容器中的广播,注入自定义线程池-->
    <bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
        <property name="taskExecutor" ref="myExecutor" />
    </bean>
  • 定义一个创建订单事件
/**
 * 创建订单完成事件
 */
@Component
public class AfterCreateOrderEvent extends ApplicationEvent {

    public AfterCreateOrderEvent(Order order) {
        super(order);
    }

    public Order getOrder(){
        return (Order) getSource();
    }
}
  • 使用事件机制改变原有代码
@Service
public class OrderService {
    //直接注入spring事件发布者
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    /**
     * 简单的创建订单方法
     */
    public void createOrder(Order order) throws InterruptedException {
        System.out.println("创建订单 order:"+order.getOrderNo()+" 结束");
        //调用事件发布者发布事件
        applicationEventPublisher.publishEvent(new AfterCreateOrderEvent(order));
        System.out.println("createOrder方法 结束");
    }

     //加入@EventListener注解后,该方法可以看出一个事件监听者
    @EventListener
    public void afterCreateOrder(AfterCreateOrderEvent afterCreateOrderEvent) throws InterruptedException {
        Order order = afterCreateOrderEvent.getOrder();
        Thread.sleep(2000);
        System.out.println("调用短信通知服务:" + order.getPhone());
        System.out.println("调用积分服务增加贷款积分:"+order.getOrderNo());
    }

    public static void main(String[] args) throws InterruptedException {
        Order order = new Order("N123124124124", "18782202534");
        //这里指定自己的spring配置路径
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("config/spring-config.xml");

        OrderService orderService = context.getBean(OrderService.class);
        orderService.createOrder(order);
    }
}

输出:

创建订单 order:N123124124124 结束
createOrder方法 结束
调用短信通知服务:18782202534
调用积分服务增加贷款积分:N123124124124

自此,创建订单与其他操作便实现了异步和解耦。

另一种异步实现方式

另外,也可使用@Async注解来实现事件的异步调用

    @EventListener
    @Async
    public void afterCreateOrder(AfterCreateOrderEvent afterCreateOrderEvent) throws InterruptedException {
        Order order = afterCreateOrderEvent.getOrder();
        Thread.sleep(2000);
        System.out.println("调用短信通知服务:" + order.getPhone());
        System.out.println("调用积分服务增加贷款积分:"+order.getOrderNo());
    }

spring配置加上:

    <!--开启异步调用,并指定线程池--> 
    <task:annotation-driven executor="annotationExecutor" />
    <!--线程池-->
    <task:executor id="annotationExecutor" pool-size="20"/>

但这种方法有弊端,afterCreateOrder()方法不能放在同一类(OrderService)里面。原因是spring的代理机制。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值