SpringFramework事件与监听机制(泛型事件)

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

运行结果符合预期。

猜你喜欢

转载自blog.csdn.net/yyb_gz/article/details/108183881