Spring 的事件发布机制
0 概述
- 在 Spring 框架中,事件发布机制是一种非常重要的通信方式,它允许不同的组件之间进行松耦合的交互。通过事件发布机制,一个组件可以发布事件,而其他组件可以监听这些事件并在事件发生时执行相应的操作。
1 使用场景
- 状态通知:当一个对象的状态发生变化时,可以发布事件来通知其他对象。例如,订单状态从“待支付”变为“已支付”时,可以发布一个事件,通知相关系统进行后续处理。
- 业务解耦:通过事件,可以将业务逻辑分散到不同的组件中,降低系统的耦合度。例如,用户注册成功后,可以发布一个用户注册事件,由邮件服务监听并发送欢迎邮件,由积分服务监听并赠送积分。
- 异步处理:事件监听器可以异步地处理事件,提高系统的响应性能。例如,当用户上传头像后,可以发布一个头像上传事件,由后台服务异步处理图片的压缩和存储。
2 优缺点
- 优点:
- 松耦合:事件的发布者和监听者不需要直接依赖对方,降低了系统的耦合度。
- 灵活:事件的监听和处理逻辑可以动态地添加或移除,提高了系统的可扩展性。
- 异步:事件的处理可以是异步的,提高了系统的性能。
- 缺点:
- 性能开销:事件机制相比直接调用会引入额外的性能开销,特别是在高并发场景下。
- 复杂性:随着系统中事件和监听器的增多,事件的管理和调试可能会变得更加复杂。
- 一致性问题:如果事件的处理是异步的,需要特别注意保证数据的一致性和事务的完整性。
3 Demo 示例
-
需求:
用户登录成功之后,需要做以下操作:
1、【系统服务】需要记录用户登录时间
2、【账户服务】需要自动给用户增加积分
3、【营销服务】需要给用户推送优惠券 -
下面我将针对用户登录场景的需求进行 Spring 事件发布和监听的示例演示:
-
登录实体对象:
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * @Author Jasper * @Time 2024/02/04 * @公众号:EzCoding */ @AllArgsConstructor @NoArgsConstructor @Data public class User { private Long id; private String name; private String password; private String sex; private Integer age; public User(String name, String password) { this.name = name; this.password = password; } }
-
定义事件:
import org.springframework.context.ApplicationEvent; import top.ezjava.java17demo.pojo.User; /** * 登录事件 * @Author Jasper * @Time 2024/02/04 * @公众号:EzCoding */ public class LoginEvent extends ApplicationEvent { /** * @param source 代表是谁登录成功 */ public LoginEvent(User source) { super(source); } }
-
创建【系统服务】事件监听器:
import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import top.ezjava.java17demo.event.LoginEvent; import top.ezjava.java17demo.pojo.User; /** * 系统服务 * @Author Jasper * @Time 2024/02/04 * @公众号:EzCoding */ @Service public class SysServiceImpl { @EventListener public void onEvent(LoginEvent event) { System.out.println("-----系统服务收到了事件-----"); User source = (User) event.getSource(); recordLogin(source.getName()); } public void recordLogin(String username) { System.out.println(username + " 登录成功,时间:" + System.currentTimeMillis()); } }
-
创建【账户服务】事件监听器:
import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Service; import top.ezjava.java17demo.event.LoginEvent; import top.ezjava.java17demo.pojo.User; /** * 账户服务 * @Author Jasper * @Time 2024/02/04 * @公众号:EzCoding */ public class AccountServiceImpl implements ApplicationListener<LoginEvent> { @override public void onApplicationEvent(LoginEvent event) { System.out.println("-----账户服务收到了事件-----"); User source = (User) event.getSource(); addAccountScore(source.getName()); } public void addAccountScore(String username) { System.out.println(username + " 增加了 10 积分"); } }
-
创建【营销服务】事件监听器:
import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import top.ezjava.java17demo.event.LoginEvent; import top.ezjava.java17demo.pojo.User; /** * 营销服务 * @Author Jasper * @Time 2024/02/04 * @公众号:EzCoding */ public class CouponServiceImpl { @EventListener public void onEvent(LoginEvent event) { System.out.println("-----营销服务收到了事件-----"); User source = (User) event.getSource(); sendCoupon(source.getName()); } public void sendCoupon(String username) { System.out.println(username + " 得到一张 10 元优惠券"); } }
-
事件发布器:
import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.stereotype.Component; /** * 自定义事件发送器 * @Author Jasper * @Time 2024/02/04 * @公众号:EzCoding */ @Component public class EventPublisher implements ApplicationEventPublisherAware { /** * 实现ApplicationEventPublisherAware接口,所有监听这个事件的监听器都可以收到 */ private ApplicationEventPublisher applicationEventPublisher; /** * 这个方法是自己写的发送事件的方法,最终会调用 Spring 的事件发送器 * @param event 事件 */ public void sendEvent(ApplicationEvent event) { // 最终调用 Spring 的事件发送器 applicationEventPublisher.publishEvent(event); } /** * setter 注入 * @param applicationEventPublisher event publisher to be used by this object */ @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } }
-
Controller 层:
import jakarta.annotation.Resource; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import top.ezjava.java17demo.event.EventPublisher; import top.ezjava.java17demo.event.LoginEvent; import top.ezjava.java17demo.pojo.User; /** * @Author Jasper * @Time 2024/02/04 * @公众号:EzCoding */ @RestController public class LoginController { @Resource private EventPublisher eventPublisher; @GetMapping("/login") public String login(@RequestParam("username") String username, @RequestParam("password") String password) { // 登录业务处理 System.out.println("登录业务处理完成..."); // 创建事件信息,并发送 LoginEvent loginEvent = new LoginEvent(new User("Jasper", "EzCoding")); eventPublisher.sendEvent(loginEvent); return username + "登陆成功"; } }
-
访问测试:localhost:8080/login?username=Jasper&password=Ezcoding
-
控制台输出结果如下:
登录业务处理完成... -----账户服务收到了事件----- Jasper 增加了 10 积分 -----营销服务收到了事件----- Jasper 得到一张 10 元优惠券 -----系统服务收到了事件----- Jasper 登录成功,时间:1706975462642
-
这样就完成了上述需求,用户登录成功后,对其余三个事件进行解耦执行
-
以上就是 Spring 事件发布机制的全部内容了,其他小伙伴还有补充的话,可以在评论区讨论~
-
创作不易,感谢阅读,若遇到问题,可以关注此微信gzh:EzCoding 留言反馈,希望能够帮助到您