大家好,我是程序员牛牛,《AI超级个体: ChatGPT与AIGC实战指南》的参与人,10年Java编程程序员。
1. 概述
在做业务开发过程中,有些复杂点的逻辑,可能代码逻辑会很冗长,举一个很简单的例子,如:用户购买产品下单支付,当支付完成后,可能有以下操作:
如果这些都在一个流程中同步执行下来,不仅代码冗长,耦合度高,而且也不方便维护,此时我们需要做的就是把这三个步骤进行异步解耦,我们第一个想到解决方案的可能是使用消息队列,MQ确实可以解决这个问题,但MQ是比较复杂的,非必要不提升架构复杂度。
如果是微服务架构,涉及到多个服务之间协作,那MQ无疑是最佳选择,但如果是单体架构,完全可以使用更加轻量级的解决方案:Spring Event
2. Spring Event简介
事件是框架中最容易被忽视的功能之一,但也是最有用的功能之一。与 Spring 中的许多其他功能一样,事件发布是ApplicationContext
提供的功能之一,它类似发布订阅机制,发布一个事件之后,可以在其他地方监听这个事件,做一些异步处理(当然也支持同步),其实它就是一个观察者模式设计。
3. 使用Spring Event
spring event使用其实很简单:
下面我们已发送邮件为例,来做一个简单的演示
3.1 其中事件监听的方式有两种
-
通过监听器的方式
-
通过注解的方式
3.2 通过监听器方式监听事件
3.2.1 定义事件
定义邮件发送事件,需要继承ApplicationEvent
, 这里我们列举了两个简单的属性,邮箱地址和邮件内容。
@Getter
@Setter
public class EmailSendEvent extends ApplicationEvent {private String address;private String content;public EmailSendEvent(Object source, String address, String content) {super(source);this.address = address;this.content = content;}
}
3.2.2 定义事件发布者
事件发布类,需要实现ApplicationEventPublisherAware
接口, 并且需要把该对象注入到spring容器中
@Component
public class EmailEventPublisher implements ApplicationEventPublisherAware {@Resourceprivate ApplicationEventPublisher publisher;@Overridepublic void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {this.publisher = applicationEventPublisher;}/*** 发送邮件** @param address 邮件地址* @param content 邮件内容*/public void sendEmail(String address, String content) {// 发送邮件的逻辑System.out.println("发送邮件:" + address + "," + content);// 发布邮件发送事件publisher.publishEvent(new EmailSendEvent(this, address, content));System.out.println("邮件发送完毕!");}}
3.2.3 创建事件监听类
这里要实现ApplicationListener
接口,且同样要把对象注入到Spring容器内
@Component
public class EmailEventListener implements ApplicationListener<EmailSendEvent> {@Overridepublic void onApplicationEvent(EmailSendEvent event) {// 因为此处是同步执行,可以发现,这里收到邮件之后,前面的邮件发送才算完成// 如果需要异步,可以使用注解方式System.out.println("监听器方式,收到邮件:" + event.getAddress() + "," + event.getContent());}}
3.2.4 测试效果
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestEmailDemo {@Autowiredprivate EmailEventPublisher emailEventPublisher;@Testpublic void testSendEmail() {String address = "test@example.com";String content = "Hello, World!";emailEventPublisher.sendEmail(address, content);}
}
此时我们运行该测试类,正常情况下,EmailEventListener
类中,将输出收到邮件的信息,我们看看效果
结果也和我们预想的一致,实际业务中,我们就可以在监听器中,做一些具体的逻辑处理,如把邮件内容发送给具体的用户等等…
3.3 通过注解方式监听事件
在spring4.2版本之后,可以直接使用注解的方式监听事件
事件定义和事件发布,和上面一致,我们增加一个注解监听的方式。
@Component
public class EmailService {/*** 使用注解方式,监听时间* @param sendEvent: email事件*/@EventListener(EmailSendEvent.class)public void receiveEmail(EmailSendEvent sendEvent) {System.out.println("注解监听方式,收到邮件:" + sendEvent.getAddress() + "," + sendEvent.getContent());}}
此时我们再运行上面的测试类,得到的结果:
可以看到两种监听方式都生效了(ps: 正常使用时,我们只需要选择一种监听方式即可)
3.4 同步事件和异步事件
3.4.1 同步事件
默认的spring事件,是同步的,也就是说,事件发送者,需要等到事件被监听完成,才算是一个事件发送完成。
按我们这个例子,是邮件监听完成后,才算邮件事件发送完成,我们来测试一下。
在监听器方式中,加入以下代码:
public void onApplicationEvent(EmailSendEvent event) {// 此处睡眠10秒try {Thread.sleep(1000 * 10);} catch (InterruptedException e) {throw new RuntimeException(e);}// 因为此处是同步执行,可以发现,这里收到邮件之后,前面的邮件发送才算完成// 如果需要异步,可以使用注解方式System.out.println("监听器方式,收到邮件:" + event.getAddress() + "," + event.getContent());}
此时执行测试类,应该是10s后,才算是邮件事件发布完成,看看实际效果:
对于一些同步场景,我们可以直接使用监听器方式,然后我们更多场景,应该是使用异步事件
3.4.2 异步事件
先使用@EnableAsync
注解,开启异步事件支持
@SpringBootApplication
@EnableAsync
public class Main {public static void main(String[] args) {SpringApplication.run(Main.class, args);}
}
然后修改EmailService
代码,加入@Async
注解,来开启异步模式
@EventListener(EmailSendEvent.class)@Async@Order(1)public void receiveEmail(EmailSendEvent sendEvent) {try {Thread.sleep(5000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("注解监听方式,收到邮件:" + sendEvent.getAddress() + "," + sendEvent.getContent());}
同时我们把监听器方式类,注释掉(只保留异步监听方式)。
此时再执行测试类的时候,应该是邮件事件发送后,不用等待事件监听完成执行,就算是该事件已经发送完成了。
3.4.3 事件监听顺序
当一个事件,我们有多个监听器时,可以可以使用@Order
注解,来指定监听器的顺序,这里@Order(1)
来指定这里的顺序为1,可以看看这个注解的源码,默认值为Integer.MAX_VALUE
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {int value() default Integer.MAX_VALUE;
}
我们在EmailService
中,增加一个监听器,并指定receiveEmail
的顺序为100,receiveEmail2
的顺序为1,此时应该先执行receiveEmail2
/*** 使用注解方式,监听时间* @param sendEvent: email事件*/@EventListener(EmailSendEvent.class)@Async@Order(100)public void receiveEmail(EmailSendEvent sendEvent) {
// try {
// Thread.sleep(5000);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }System.out.println("注解监听方式1,收到邮件:" + sendEvent.getAddress() + "," + sendEvent.getContent());}/*** 使用注解方式,监听时间* @param sendEvent: email事件*/@EventListener(EmailSendEvent.class)@Async@Order(1)public void receiveEmail2(EmailSendEvent sendEvent) {System.out.println("注解监听方式2,收到邮件:" + sendEvent.getAddress() + "," + sendEvent.getContent());}
看看测试结果:
跟预期一致!
最后我把测试源码都放到码云上了,欢迎关注公众号获取源码!发送消息“Spring Event”即可。