Spring Events 详解:解锁事件驱动架构

目录

一、简介

二、Spring Events

三、案例

        3.1 事件监听器的实现方式

四、源码实现

五、总结


一、简介

        这篇文章介绍一下事件驱动相关内容,首先看一个场景,假如有一个系统,用户注册后需要给用户发送邮箱、短信等欢迎提醒,同时还需要给用户发送首条站内短消息,如果让你做这个功能你会怎么做,你可能会觉得这还不简单吗,把这三个功能封装成相应的方法,然后用户注册后分别调一下不就可以了。

        的确这种方式是可以实现,后期可能会加新的需求,继续在后边调用其他功能方法,最后你发现这调来调去没完了,可能用户注册后边跟了 n 各方法的调用,很恶心,于是需要优化一下,你怎么解决呢?

        这时可以使用事件驱动架构,当然你也可以通过消息中间件实现,但是这种方式相对较重,这里重点介绍一下事件驱动。

二、Spring Events

        事件驱动是一种编程范式,其中程序的执行是由外部事件触发的,而不是通过预先定义好的顺序执行的。在这种模式下,程序不断地监听事件源,当检测到事件发生时,就会执行相应的代码。

        Spring 内部提供了这种事件驱动的机制,那就是 Spring Events,它是观察者涉及模式的实现。它增强了不同组件之间的通信,超越了传统的方法调用,并且基于松耦合架构,实现了解耦,减少了模块间的依赖。

        Spring Events 的核心是三个组件:事件、发布者和监听器。

        定义事件时需要实现 ApplicationEvent,事件在发布者和监听器之间传输数据。发布者创建此事件对象并使用 ApplicationEventPublisher 来发布它,监听器可以通过多种方式来实现,如注释(@EventListener)或实现 ApplicationListenter接口。

三、案例

        下面就来实现一下上面提到的用户注册的案例。

        首先是定义事件

public class UserDTO extends ApplicationEvent {

    private Integer userId;

    private String name;

    private Integer age;


    /**
     * 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 UserDTO(Object source) {
        super(source);
    }
}

        定义三种不同的事件监听器

// 发送短信的监听器
@Component
public class UserRegisterSmsListener implements SmartApplicationListener {

    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
        return eventType == UserDTO.class;
    }

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

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        // 这里完成短信发送
        System.out.println("监听到用户注册,准备发送短信, user:" + event.toString());
    }
}
// 发送邮件的监听器
@Component
public class UserRegisterEmailListener implements SmartApplicationListener {

    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
        return eventType == UserDTO.class;
    }

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

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        // 完成邮件推送
        System.out.println("监听到用户注册,准备发送邮件,user:" + event.toString());
    }
}
// 给用户发送首条站内消息的监听器
@Component
public class UserRegisterMessageListener implements SmartApplicationListener {

    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
        return eventType == UserDTO.class;
    }

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        // 完成消息推送
        System.out.println("监听到用户注册,给新用户发送首条站内短消息,user:" + event.toString());
    }

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

        最后是事件发布

@Service
public class UserServiceImpl implements UserService{

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    @Override
    public void register() {
        UserDTO userDTO = new UserDTO(this);
        userDTO.setAge(18);
        userDTO.setName("admol");
        userDTO.setUserId(1001);
        // 这里是注册的流程,不如入库等操作
        System.out.println("register user");
        // 完成注册后,发布事件
        eventPublisher.publishEvent(userDTO);
    }
}


@SpringBootApplication
public class EventApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(EventApplication.class, args);
        // 这里简化一些,服务启动时就发布事件,当然正常流程肯定是通过接口来实现的,这里只是验证功能而已,不要纠结细节
        UserService userService = context.getBean(UserService.class);
        // 用户注册,触发时间发布
        userService.register();
    }

}

        程序执行后就可看到相应的结果。

        3.1 事件监听器的实现方式

        Spring Events 提供了几种不同方式来实现事件监听器,根据不同的需求和场景来选择合适的实现。

  • 实现 ApplicationListener 接口:这是最直接的方式,也是最常用的方式,需要重写 onApplicationEvent 方法
  • 使用 @EventListener 注解:Spring 3.0 引入了 @EventListener 注解,这是一种更加简洁的方式来实现事件监听。你可以使用此注解来标记方法,使之成为事件监听器。
  • 使用 SmartApplicationListener:SmartApplicationListener 接口扩展了 ApplicationListener 接口,并添加了一些额外的方法来提供更智能的事件处理能力。

        SmartApplicationListener 扩展的功能如下:

  •  事件类型支持(重写 supportEventType):确定当前事件监听器是否支持特定类型的事件。在事件分发之前,可以过滤掉不敢兴趣的事件,从而提高性能。
  • 事件源支持(通过重写 supportSourceType):确定当前事件监听器是否支持来自特定类型的事件源。可以进一步细化事件处理范围,只处理来自特定类型的事件源事件。
  • 事件处理前的准备工作(重写 prepareForEventProcessing):在事件处理开始之前执行某些准备工作。
  • 事件处理后的清理工作(重写 completeForEventProcessing):在事件处理结束之后执行某些清理工作。
  • 事件处理顺序(重写 getOrder):这里定义事件的处理顺序,就不如上边的案例中设置的那样。

四、源码实现

        分析源码的流程需要从事件发布的位置开始看

             然后进入到 publishEvent 的方法中,最终会调用 AbstractApplicationContext.publishEvent方法,具体源码如下:

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

		// 将事件装饰为 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) {
			this.earlyApplicationEvents.add(applicationEvent);
		}
		else {
            // 通过 getApplicationEventMulticaster 方法获取事件发布器
            // 调用multicastEvent方法发布事件
			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);
			}
		}
	}

        然后就会进入到 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);
			}
		}
}
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
		ErrorHandler errorHandler = getErrorHandler();
		if (errorHandler != null) {
			try {
				doInvokeListener(listener, event);
			}
			catch (Throwable err) {
				errorHandler.handleError(err);
			}
		}
		else {
            // 具体执行事件监听器
			doInvokeListener(listener, event);
		}
	}


private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
		try {
            // 执行onApplicationEvent方法
			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;
			}
		}
	}

        到这里就可以找到具体的实现来执行了

        如果想实现异步执行事件,可以将 @Async 注解加到监听器上来实现,或者手动实现异步线程池。在高负载的场景下可以使用异步处理事件的机制来提高事件处理的性能。

五、总结

        Spring Events 是 Spring 框架的一部分,它提供了一种机制来处理事件驱动的架构。这种机制允许开发者在应用程序的不同组件之间传递事件,从而使得这些组件可以松耦合地相互协作。        

        随着异步编程模式的流行,Spring Events 将会提供更多内置支持,使得异步事件变得更加简单高效。

往期经典推荐

Redis中的潜在阻塞点及其解决方案-CSDN博客

从理论到实践:零拷贝技术的全面解读_介绍一下零拷贝-CSDN博客

深入浅出 Spring @Async 异步编程的艺术-CSDN博客

Sentinel与Nacos强强联合,构建微服务稳定性基石的重要实践_nacos sentinel-CSDN博客

SpringBoot项目并发处理大揭秘,你知道它到底能应对多少请求洪峰?_一个springboot能支持多少并发-CSDN博客

揭开Spring Bean生命周期的神秘面纱_spring bean的生命周期 代理-CSDN博客

  • 40
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超越不平凡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值