SpringBoot版本:2.0.2.RELEASE
SpringFramework版本:5.0.6.RELEASE
实现泛型的机制
前了前面几篇文章的铺陈,这个话题就容易描述了,读者可顾回这几篇文章:
- 《SpringFramework事件与监听机制(发布器)》
- 《SpringFramework事件与监听机制(事件)》
- 《SpringFramework事件与监听机制(监听器)》
泛型事件的发布
AbstractApplicationContext作为ApplicationEventPublisher接口的实现类,它承担起事件发布任务。在重写的publishEvent方法的关键代码如下:
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
....
// Decorate event as an ApplicationEvent if necessary
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}
else {
applicationEvent = new PayloadApplicationEvent<>(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent) applicationEvent).getResolvableType();
}
}
// Multicast right now if possible - or lazily once the multicaster is initialized
if (this.earlyApplicationEvents != null)
{
this.earlyApplicationEvents.add(applicationEvent);
}
else {
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
....
}
方法在发布事件前,先判断事件是否ApplicationEvent类型,如果不是就封装成PayloadApplicationEvent事件。
public class PayloadApplicationEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {
private final T payload;
/**
* Create a new PayloadApplicationEvent.
* @param source the object on which the event initially occurred (never {@code null})
* @param payload the payload object (never {@code null})
*/
public PayloadApplicationEvent(Object source, T payload) {
super(source);
Assert.notNull(payload, "Payload must not be null");
this.payload = payload;
}
@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getPayload()));
}
/**
* Return the payload of the event.
*/
public T getPayload() {
return this.payload;
}
}
从类的定义上看,PayloadApplicationEvent继承于ApplicationEvent。PayloadApplicationEvent实现ResolvableTypeProvider接口,它定义的方法返回的ResolvableType是将PayloadApplicationEvent的类型以及以payload属性的具体类型作为的泛型揉合在一起的结合物作为事件的类型。它以PayloadApplicationEvent类型为原生类型,以payload属性的具体类型为泛型。在此我们可以想象一下,在类型定义层面,我们为类添加了泛型标记,这样子明确了类是带泛型的,泛型的具体类型是不清楚的,在真正使用到这类型的时候才会指定泛型的真正类型。而Spring Framework封装的ResolvableType在运行时可将两个不同的类型揉合到一起,作用在事件与监听机制的过虑中能起到甄别监听器的作用,这设计摆脱了类定义的固定模式,让类之间的结合变得灵活起来,真是高明。相信在Spring Framework其他方面也会起到类似的作用。
在AbstractApplicationContext#publishEvent方法体里可知,PayloadApplicationEvent以ConfigurableApplicationContext作为事件源,目前还不清楚这样处理的用意何在。
在《SpringFramework事件与监听机制(监听器)》里描述过,SimpleApplicationEventMulticaster在发布事件时,会调用父类的getApplicationListeners方法,该方法会返回与事件相匹配的Listener集合。那么如何甄别事件类型监听器是否匹配,就得看AbstractApplicationEventMulticaster#supportsEvent方法:
protected boolean supportsEvent(
ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {
GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
(GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
}
在此,无论监听器是否GenericApplicationListener接口的实现类,都会统一成GenericApplicationListener接口类型,通过supportsEventType方法和supportsSourceType方法来判断该监听器是否与事件类型匹配。读者可看看《SpringFramework事件与监听机制(监听器)》的“监听器实现的接口”部分。
因为PayloadApplicationEvent继承于ApplicationEvent,所以当直接实现ApplicationListener接口并指定ApplicationEvent泛型的监听器,都会监听到PayloadApplicationEvent事件,因为该事件是ApplicationEvent的子类。
监听泛型事件
我们重温监听器的UML:
无论直接实现ApplicationListener接口还是实现该接口的子接口的监听器,都要重载onApplicationEvent方法。该方法的入参的适用类型为ApplicationEvent及其子类,因此也会包括PayloadApplicationEvent事件。根据PayloadApplicationEvent的构造函数,以及调用该构造函数的场景(AbstractApplicationContext#publishEvent方法体),事件源为ConfigurableApplicationContext,泛型的运行时具体对象被存储为payload字段。对于泛型事件来说,payload字段的对象才是事件的真正内容。所以打算监听PayloadApplicationEvent事件的监听器在重载onApplicationEvent时需要将入参的参数转换成PayloadApplicationEvent类型,并获取期payload字段内容。
测试
测试的目的是发布以User为类型的泛型事件。
启动SpringBoot并发布User泛型事件:
public class MyClass<T> {
public static void main(String[] args){
ConfigurableApplicationContext context = SpringApplication.run(WebConfiguration.class,args);
context.publishEvent( new User("yyb"));
}
public static class User{
private final String name;
public User(String name){
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
}
运行泛型的监听器:
public class CustomerizedListener1 implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof PayloadApplicationEvent){
System.out.println( Thread.currentThread().getName() + ":CustomerizedListener1:" + event.getClass().getName() + " content:"+ ((PayloadApplicationEvent)event).getPayload());
}else{
System.out.println(Thread.currentThread().getName() + ":CustomerizedListener1:" + event.getClass().getName());
}
}
}
CustomerizedListener1 监听的事件是ApplicationEvent,因此SpringBoot的启停过程所有事件都会监听到。另外对于泛型事件,会取出该事件的payload属性。
META-INF/spring.factories的内容如下:
org.springframework.context.ApplicationListener=test.listener.CustomerizedListener1
运行结果如下:
D:\Software\Java\jdk1.8.0_111\bin\java.exe "-javaagent:D:\Software\JetBrains\IntelliJ IDEA Community Edition 2019.3.4\lib\idea_rt.jar=57004:D:\Software\JetBrains\IntelliJ IDEA Community Edition 2019.3.4\bin" -Dfile.encoding=UTF-8 -classpath D:\Software\Java\jdk1.8.0_111\jre\lib\charsets.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\deploy.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\ext\access-bridge-64.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\ext\cldrdata.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\ext\dnsns.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\ext\jaccess.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\ext\jfxrt.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\ext\localedata.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\ext\nashorn.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\ext\sunec.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\ext\sunjce_provider.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\ext\sunmscapi.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\ext\sunpkcs11.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\ext\zipfs.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\javaws.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\jce.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\jfr.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\jfxswt.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\jsse.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\management-agent.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\plugin.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\resources.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\rt.jar;D:\Temp\mywebapp\target\classes;D:\m2_repository\org\springframework\boot\spring-boot-loader\2.0.2.RELEASE\spring-boot-loader-2.0.2.RELEASE.jar;D:\m2_repository\org\springframework\boot\spring-boot-starter\2.0.2.RELEASE\spring-boot-starter-2.0.2.RELEASE.jar;D:\m2_repository\org\springframework\boot\spring-boot\2.0.2.RELEASE\spring-boot-2.0.2.RELEASE.jar;D:\m2_repository\org\springframework\spring-context\5.0.6.RELEASE\spring-context-5.0.6.RELEASE.jar;D:\m2_repository\org\springframework\spring-aop\5.0.6.RELEASE\spring-aop-5.0.6.RELEASE.jar;D:\m2_repository\org\springframework\spring-beans\5.0.6.RELEASE\spring-beans-5.0.6.RELEASE.jar;D:\m2_repository\org\springframework\spring-expression\5.0.6.RELEASE\spring-expression-5.0.6.RELEASE.jar;D:\m2_repository\org\springframework\boot\spring-boot-autoconfigure\2.0.2.RELEASE\spring-boot-autoconfigure-2.0.2.RELEASE.jar;D:\m2_repository\org\springframework\boot\spring-boot-starter-logging\2.0.2.RELEASE\spring-boot-starter-logging-2.0.2.RELEASE.jar;D:\m2_repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;D:\m2_repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;D:\m2_repository\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;D:\m2_repository\org\apache\logging\log4j\log4j-to-slf4j\2.10.0\log4j-to-slf4j-2.10.0.jar;D:\m2_repository\org\apache\logging\log4j\log4j-api\2.10.0\log4j-api-2.10.0.jar;D:\m2_repository\org\slf4j\jul-to-slf4j\1.7.25\jul-to-slf4j-1.7.25.jar;D:\m2_repository\javax\annotation\javax.annotation-api\1.3.2\javax.annotation-api-1.3.2.jar;D:\m2_repository\org\springframework\spring-core\5.0.6.RELEASE\spring-core-5.0.6.RELEASE.jar;D:\m2_repository\org\springframework\spring-jcl\5.0.6.RELEASE\spring-jcl-5.0.6.RELEASE.jar;D:\m2_repository\org\yaml\snakeyaml\1.19\snakeyaml-1.19.jar;D:\m2_repository\com\fasterxml\jackson\core\jackson-databind\2.9.5\jackson-databind-2.9.5.jar;D:\m2_repository\com\fasterxml\jackson\core\jackson-annotations\2.9.0\jackson-annotations-2.9.0.jar;D:\m2_repository\com\fasterxml\jackson\core\jackson-core\2.9.5\jackson-core-2.9.5.jar test.MyClass
main:CustomerizedListener1:org.springframework.boot.context.event.ApplicationStartingEvent
main:CustomerizedListener1:org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.2.RELEASE)
2020-08-23 23:02:00.608 INFO 1252 --- [ main] test.MyClass : Starting MyClass on yangyongbin-PC with PID 1252 (D:\Temp\mywebapp\target\classes started by yangyongbin in D:\Temp\mywebapp)
2020-08-23 23:02:00.614 INFO 1252 --- [ main] test.MyClass : No active profile set, falling back to default profiles: default
main:CustomerizedListener1:org.springframework.boot.context.event.ApplicationPreparedEvent
2020-08-23 23:02:00.688 INFO 1252 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6591f517: startup date [Sun Aug 23 23:02:00 CST 2020]; root of context hierarchy
2020-08-23 23:02:02.246 INFO 1252 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
main:CustomerizedListener1:org.springframework.context.event.ContextRefreshedEvent
2020-08-23 23:02:02.262 INFO 1252 --- [ main] test.MyClass : Started MyClass in 2.015 seconds (JVM running for 2.59)
main:CustomerizedListener1:org.springframework.boot.context.event.ApplicationStartedEvent
main:CustomerizedListener1:org.springframework.boot.context.event.ApplicationReadyEvent
main:CustomerizedListener1:org.springframework.context.PayloadApplicationEvent content:User{
name='yyb'}
2020-08-23 23:02:02.268 INFO 1252 --- [ Thread-2] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@6591f517: startup date [Sun Aug 23 23:02:00 CST 2020]; root of context hierarchy
Thread-2:CustomerizedListener1:org.springframework.context.event.ContextClosedEvent
2020-08-23 23:02:02.270 INFO 1252 --- [ Thread-2] o.s.j.e.a.AnnotationMBeanExporter : Unregistering JMX-exposed beans on shutdown
Process finished with exit code 0
运行结果符合预期。