使用Spring Event实现内部模块间的轻松解耦

 更新时间:2025年08月28日 08:51:52   作者:磊磊落落  
Spring Event 是 Spring 框架提供的一个核心组件,其允许服务内部不同模块之间通过观察者模式(发布-订阅模式)进行通信,从而实现模块间的解耦,本文给大家介绍了如何使用 Spring Event 实现内部模块间的轻松解耦,需要的朋友可以参考下

Spring Event 是 Spring 框架提供的一个核心组件,其允许服务内部不同模块之间通过观察者模式(发布-订阅模式)进行通信,从而实现模块间的解耦。

即 Spring Event 是一种事件驱动的编程模型,一个模块在做完一件事后,无需直接调用其它模块处理后续逻辑,而是发布一个事件出来,由其它对该事件感兴趣的模块订阅并处理这个事件,事件发布者无需关注订阅者是谁,从而实现模块间的轻松解耦。

Spring Event 的使用非常的广泛,包含但不限于:用户注册成功后的后续操作(如发送欢迎邮件、发放新用户优惠券、初始化积分等);订单状态的变更通知(订单支付成功后通知库存系统减库存、通知物流系统准备发货等);系统日志记录(重要数据被修改后通知日志系统记录变更字段、通知管理员进行安全检查和审计等)。

除了上述业务场景外,Spring Event 有时还能很巧妙的解决一些技术问题:比如解决 Spring 父子模块的通信。实际项目中,Spring Boot 工程通常会依赖一个 SharedModule 父模块,这个 SharedModule 父模块里拥有一些公共的 POJO 类、数据库 Entity 类、Util 类等,如果想在父模块里调用子模块的方法来实现一些逻辑,在技术上是有一点难度的。但通过借助 Spring Event,父模块只需增加一个事件,然后发布即可,在子模块中监听并处理就好了。

介绍了 Spring Event 是什么以及其适用的场景外。本文将以「用户注册成功后发送邮件、发放优惠券」为例,创建一个 Spring Boot 示例程序,来演示 Spring Event 的使用。

下面列出写作本文时用到的 Java、Spring Boot 以及 Spring 框架的版本:

Java: 17
Spring Boot: 3.5.5
Spring Framework: 6.2.10

1 Spring Event 如何使用?

如何使用 Spring Event 呢?只有三个步骤:定义事件、发布事件、监听事件。

1.1 定义事件(Event)

自 Spring 4.2 后,定义事件时,无需再继承 ApplicationEvent。任何一个普通的 Java POJO 都可以充当事件实体类。

下面即是我们定义的用户注册后事件:

package com.example.demo.model;

@Builder
@Data
public class UserRegisteredEvent {

    private String email;
    private String username;
}

1.2 发布事件(Event Publisher)

下面即是事件发布者 UserServiceImpl 在保存 User 后,调用 ApplicationEventPublisher 发布 UserRegisteredEvent 的代码:

package com.example.demo.service.impl;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    @Override
    public void save(User user) {
        // save user
        // userRepository.save(user);

        // publish event
        UserRegisteredEvent event = UserRegisteredEvent.builder()
                .email(user.getEmail())
                .username(user.getUsername())
                .build();

        eventPublisher.publishEvent(event);

        LOGGER.info("user registered event successfully published");
    }
}

可以看到,上述代码在 save User 后,发布了一个 UserRegisteredEvent,并打印了一段事件成功发布的日志。

1.3 监听事件 (Event Listener)

自 Spring 4.2 后,订阅者要想监听事件,无需再实现 ApplicationListener 接口,而只需添加一个接收 Event 对象的方法,并在方法上加上 @EventListener 注解即可。

一个事件可以被多个订阅者监听,下面的邮件服务、优惠券服务监听了 UserRegisteredEvent

package com.example.demo.service.impl;

@Service
public class EmailServiceImpl implements EmailService {

    @EventListener
    public void handleUserRegisteredEvent(UserRegisteredEvent event) {
        // send email
        LOGGER.info("email successfully sent to: {}", event.getEmail());
    }
}
package com.example.demo.service.impl;

@Service
public class CouponServiceImpl implements CouponService {

    @EventListener
    public void handleUserRegisteredEvent(UserRegisteredEvent event) {
        // issue coupon
        LOGGER.info("coupon successfully issued to: {}", event.getEmail());
    }
}

可以看到,上述两个 Service 实现类在接收到 UserRegisteredEvent 后,分别打印了一段日志来模拟邮件成功发送和优惠券成功发放。

1.4 测试(Testing)

下面为 UserService 编写一个单元测试类 UserServiceTest 来测试事件的发布和订阅:

package com.example.demo;

@SpringBootTest
public class UserServiceTest {

    @Autowired
    private UserService userService;

    @Test
    public void testSave() {
        User user = new User();
        // user.setXxx();

        userService.save(user);
    }
}

运行 UserServiceTest 测试类,可以看到,UserServicesave() 方法被调用后,控制台打印了如下三行日志:

CouponServiceImpl  : coupon successfully issued to: larry@larry.com
EmailServiceImpl   : email successfully sent to: larry@larry.com
UserServiceImpl    : user registered event successfully published

说明我们编写的 UserRegisteredEvent 被成功发布、订阅和处理了。

但需要注意上面日志的打印顺序:事件被处理的日志打印后才打印了事件发布的日志,这说明 Spring Event 默认是同步执行的,即 ApplicationEventPublisherpublishEvent() 方法是阻塞式的,会等待所有监听器将事件处理完毕才算调用结束。

这样,若订阅者的处理逻辑很耗时的话会影响到发布者的性能。所以,是否有方法让监听器的处理变成异步的呢?下面请看 Spring Event 的进阶用法。

2 Spring Event 进阶用法

若想让监听器的执行变成异步的,可以在程序启动类上加上 @EnableAsync 注解:

package com.example.demo;

@EnableAsync
@SpringBootApplication
public class DemoApplication {
}

然后在事件处理方法上加上 @Async 注解,即能使事件处理方法变成异步执行。

package com.example.demo.service.impl;

@Service
public class EmailServiceImpl implements EmailService {

    @Async
    @EventListener
    public void handleUserRegisteredEvent(UserRegisteredEvent event) {
        // send email
        LOGGER.info("email successfully sent to: {}", event.getEmail());
    }
}

再次运行 UserServiceTest 测试类,发现发布者与订阅者的日志打印顺序变成了:

UserServiceImpl    : user registered event successfully published
CouponServiceImpl  : coupon successfully issued to: larry@larry.com
EmailServiceImpl   : email successfully sent to: larry@larry.com

即说明事件发布者在主线程调用 publishEvent() 方法后立即返回,而监听器的逻辑处理会在新启动的线程中执行,不会再阻塞主线程。

3 小结

综上,本文首先介绍了 Spring Event 的功能,然后以「用户注册成功后发送邮件、发放优惠券」为例,用 Spring Boot 示例程序的方式演示了 Spring Event 的使用。

经过本文的实践,说明借助 Spring Event 的确可以很轻松的实现服务内部模块间的解耦。但若是服务间的通信和解耦,以及需要更高的并发和可靠性,则还是需要引入其它第三方消息队列等工具来实现。

以上就是使用Spring Event实现内部模块间的轻松解耦的详细内容,更多关于Spring Event内部模块间解耦的资料请关注脚本之家其它相关文章!

相关文章

  • logback FixedWindowRollingPolicy固定窗口算法重命名文件滚动策略

    logback FixedWindowRollingPolicy固定窗口算法重命名文件滚动策略

    这篇文章主要介绍了FixedWindowRollingPolicy根据logback 固定窗口算法重命名文件滚动策略源码解读,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-11-11
  • Spring复杂对象创建的方式小结

    Spring复杂对象创建的方式小结

    这篇文章主要介绍了Spring复杂对象创建的三种方式,现在使用Spring如何创建这种类型的对象?Spring中提供了三种方法来创建复杂对象,需要的朋友可以参考下
    2022-01-01
  • 浅谈mybatis 乐观锁实现,解决并发问题

    浅谈mybatis 乐观锁实现,解决并发问题

    这篇文章主要介绍了浅谈mybatis 乐观锁实现,解决并发问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08
  • mybatis-plus的多租户不同版本实现的两种方式

    mybatis-plus的多租户不同版本实现的两种方式

    本文主要介绍了mybatis-plus的多租户不同版本实现的两种方式,Mybatis Plus 3.4.0版本之后多租户的实现,具有一定的参考价值,感兴趣的可以了解一下
    2025-03-03
  • 如何解决getReader() has already been called for this request问题

    如何解决getReader() has already been called&

    这篇文章主要介绍了如何解决getReader() has already been called for this request问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-05-05
  • Java线程的并发工具类实现原理解析

    Java线程的并发工具类实现原理解析

    本文给大家讲解Java线程的并发工具类的一些知识,通过适用场景分析大数据量统计类任务的实现原理和封装,多个示例代码讲解的非常详细,对java线程并发工具类相关知识感兴趣的朋友一起学习下吧
    2021-06-06
  • Java中的Optional类详细解读

    Java中的Optional类详细解读

    这篇文章主要介绍了Java中的Optional类详细解读,Optional是Java中的一个类,它的作用是用于解决空指针异常的问题,它提供了一些有用的方法,可以帮助我们避免显式进行空值检测,需要的朋友可以参考下
    2023-08-08
  • Java实现Html保存为.mhtml文件的代码逻辑

    Java实现Html保存为.mhtml文件的代码逻辑

    文章介绍了实现将HTML字符串保存为.mhtml文件的代码逻辑,包括通过URL和Cookie免密获取HTML字符串,将HTML中的图片、CSS、JS转换为base64字符串,删除不需要的布局和内容,最终将替换后的HTML保存为.mhtml文件,感兴趣的朋友跟随小编一起看看吧
    2026-01-01
  • 专属于程序员的浪漫-Java输出动态闪图iloveyou

    专属于程序员的浪漫-Java输出动态闪图iloveyou

    这篇文章主要介绍了专属于程序员的浪漫-Java输出动态闪图iloveyou,具有一定参考价值,需要的朋友可以了解下。
    2017-11-11
  • IntelliJ IDEA创建maven多模块项目(图文教程)

    IntelliJ IDEA创建maven多模块项目(图文教程)

    这篇文章主要介绍了IntelliJ IDEA创建maven多模块项目(图文教程),非常具有实用价值,需要的朋友可以参考下
    2017-09-09

最新评论