Event事件机制
Spring事件机制基于观察者模式实现了事件发布和监听的功能,Spring有一些框架内部的事件,我们也可以自定义事件。使用事件机制的好处是能够进一步解耦业务逻辑,实现低耦合、无侵入式的业务逻辑处理,也可以实现异步处理等高级功能。
基本概念
事件:Spring中的事件ApplicationEvent继承JDK的java.util.EventObject抽象类,该类包含了一个source属性保存了事件源对象的引用,以及事件发生的时间戳,Spring内置的事件和我们自定义的事件需要继承ApplicationEvent这个类来实现。
事件发布者:用于发布事件的接口,Spring框架中我们可以调用applicationContext.publishEvent()发布事件。
事件监听器:事件监听器可以使用@EventListener注解或实现ApplicationListener接口来定义,注意它必须是一个Spring的Bean,否则Spring框架是无法找到指定的监听器对象的。
事件机制的使用
监听Spring内置事件
Spring内置的事件和发布者都是Spring实现的,因此我们编写一个事件监听器即可。
package com.gacfox.demo;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class DemoComponent {
@EventListener
public void onStarted(ApplicationStartedEvent event) {
System.out.println("Spring启动完成");
}
}
代码中我们使用了@EventListener注解标注了一个方法,Spring会根据方法的参数类型,在对应事件发生时回调方法。如果参数类型使用ApplicationEvent,则所有类型的事件该方法都会接收到。
此外要注意,如果有多个方法监听同一个事件,事件发生时所有方法都会被回调,不会出现只有一个方法被调用的情况。
Spring中常用的内置事件有如下几种。
| 事件类型 | 说明 |
|---|---|
| ContextRefreshedEvent | 当Spring容器初始化或刷新时触发,即applicationContext的refresh()方法被调用后。 |
| ContextStartedEvent | 当Spring容器停止时触发,即applicationContext的start()方法被调用后。 |
| ContextStoppedEvent | 当Spring容器关闭时触发,即applicationContext的stop()方法被调用后。 |
| ContextClosedEvent | 当Spring容器启动时触发,即applicationContext的close()方法被调用后。 |
| RequestHandledEvent | 用于SpringMVC环境,当DispatcherServlet处理请求完成后触发。 |
自定义事件
自定义事件和使用内置事件类似,只不过需要我们手动创建EventObject和发射事件,下面是一个自定义事件的例子。
RegistrationEvent.java
package com.gacfox.demo;
import org.springframework.context.ApplicationEvent;
public class RegistrationEvent extends ApplicationEvent {
public RegistrationEvent(Object source) {
super(source);
}
}
DemoComponent.java
package com.gacfox.demo;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class DemoComponent {
@EventListener
public void onRegistration(RegistrationEvent event) {
System.out.println("用户注册了");
}
}
Main.java
package com.gacfox.demo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@Slf4j
public class Main {
private static ApplicationContext context;
public static void main(String[] args) {
context = new ClassPathXmlApplicationContext("applicationContext.xml");
new Main().testPublishEvent();
}
private void testPublishEvent() {
context.publishEvent(new RegistrationEvent(this));
}
}
代码中,RegistrationEvent是我们自定义的事件,它继承了ApplicationEvent,DemoComponent是我们的事件监听器,最后Main类被我们作为了发射器,它通过applicationContext对象发射事件,并由DemoComponent的onRegistration()方法接收。
异步事件
上面介绍的自定义事件监听例子其实还是一个同步式的代码,如果我们的EventListener处理事件的耗时较长,事件的发射端必须等待它处理完成后才能返回。实际上,Spring的事件机制也可以结合异步任务实现事件的异步处理,这需要我们配置异步任务声明注解的扫描和异步执行器。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">
<context:component-scan base-package="com.gacfox.demo"/>
<task:annotation-driven executor="appTaskExecutor"/>
<bean id="appTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="5"/>
<property name="maxPoolSize" value="10"/>
<property name="queueCapacity" value="100"/>
<property name="threadNamePrefix" value="AsyncTask-"/>
<property name="rejectedExecutionHandler">
<bean class="java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy"/>
</property>
</bean>
</beans>
此时事件的接收端增加一个@Async注解即可,不需要其它改动。
package com.gacfox.demo;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class DemoComponent {
@Async
@EventListener
public void onRegistration(RegistrationEvent event) {
try {
// 模拟耗时操作
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("用户注册了");
}
}
我们可以通过打印线程ID和线程名来验证是否实现了异步执行,如果异步执行,事件发布的线程和处理事件的线程应该并非同一个。
注意:在Web工程中,由于Java的Servlet API从设计之初就采用的是多线程并发模型,我们对请求进行的处理流程都是阻塞式、串行的,所有处理都发生在同一个线程中,因此许多库和框架都是依赖ThreadLocal构建的,比如SpringMVC的RequestContextHolder、日志框架的MDC等。如果引入异步处理这些API就会失效,我们需要考虑到这些潜在的问题。
有关异步任务调度的详细内容可以参考后续任务调度章节。