Spring解决泛型擦除的思路不错,现在它是我的了。-为什么?

时间:2024-01-23 13:22:17

我也不知道为什么,但是我知道源码之下无秘密。

所以,先打上断点再说。

关于 @EventListener 注解的原理和源码解析,我之前写过一篇相关的文章:《扯下@EventListener这个注解的神秘面纱。》

有兴趣的可以看看这篇文章,然后再试着按照文章中的方式去找对应的源码。

我这篇文章就不去抽丝剥茧的一点点找源码了,直接就是一个大力出奇迹。

因为我们已知是 ResolvableTypeProvider 这个接口在搞事情,所以我只需要看看这个接口在代码中被使用的地方有哪些:

除去一些注释和包导入的地方,整个项目中只有 ResolvableType 和 MultipartHttpMessageWriter 这个两个中用到了。

直觉告诉我,应该是在 ResolvableType 用到的地方打断点,因为另外一个类看起来是 Http 相关的,和我的 Demo 没啥关系。

所以我直接在这里打上断点,然后发起调用,程序果然就停在了断点处:

org.springframework.core.ResolvableType#forInstance

我们观察一下,发现这几行代码核心就干一个事儿:判断 instance 是不是 ResolvableTypeProvider 的子类。

如果是则返回一个 type,如果不是则返回 forClass(instance.getClass())。

通过 Debug 我们发现 instance 是 BaseEvent:

巧了,这就是 ResolvableTypeProvider 的子类,所以返回的 type 是这样式儿的:

com.example.elasticjobtest.BaseEvent<com.example.elasticjobtest.Person>

是带具体的类型的,而这个类型就是通过 getResolvableType 方法拿到的。

前面我们在实现 ResolvableTypeProvider 的时候,就重写了 getResolvableType 方法,调用了 ResolvableType.forClassWithGenerics,然后用 data 对应的真正的 T 对象实例的类型,作为返回值,这样泛型对应的真正的对象类型,就在运行期被动态的获取到了,从而解决了编译阶段泛型擦除的问题。

如果没有实现 ResolvableTypeProvider 接口,那么这个方法返回的就是 BaseEvent<?>:

com.example.elasticjobtest.BaseEvent<?>

看到这里你也就猜到个七七八八了。

都已经拿到具体的泛型对象了,后面再发起对应的事件监听,那不是顺理成章的事情吗?

好,现在你在第一个断点处就收获到了一个这么关键的信息,接下来怎么办呢?

接着断点处往下调试,然后把整个链路都梳理清楚呗。

再往下走,你会来到这个地方:

org.springframework.context.event.AbstractApplicationEventMulticaster#getApplicationListeners

从 cache 里面获取到了一个 null。

因为这个缓存里面放的就是在项目启动过程中已经触发过的框架自带的 listener 对象:

调用的时候,如果能从缓存中拿到对应的 listener,则直接返回。而我们 Demo 中的自定义 listener 是第一次触发,所以肯定是没有的。

因此关键逻辑就这个方法的最后一行:retrieveApplicationListeners 方法里面

org.springframework.context.event.AbstractApplicationEventMulticaster#retrieveApplicationListeners

这个地方再往下写,就是我前面我提到的这篇文章中我写过的内容了《扯下@EventListener这个注解的神秘面纱。》

和泛型擦除的关系已经不大了,我就不再写一次了。

只是给大家看一下这个方法在我们的 Demo 中,最终返回的 allListeners 就是我们自定义的这个事件监听器:

com.example.elasticjobtest.EventListenerService#handlePersonEvent

为什么是这个?

因为我当前发布的事件的主角就是 Person 对象:

同理,当 Order 对象的事件过来的时候,这里肯定就是对应的 handleOrderEvent 方法:

如果我们把 BaseEvent 的 ResolvableTypeProvider 接口拿掉,那么你再看对应的 allListeners,你就会发现找不到我们对应的自定义 Listener 了:

为什么?

因为当前事件对应的 ResolvableType 是这样的:

org.springframework.context.PayloadApplicationEvent<com.example.elasticjobtest.BaseEvent<?>>

而我们并没有自定义一个这样的 Listener:

@EventListener
public void handleAllEvent(BaseEvent<?> orderEvent) {
    log.info("监听到Event: {}", orderEvent);
}

所以,这个事件发布了,但是没有对应的消费。

大概就是这么个意思。

核心逻辑就在 ResolvableTypeProvider 接口里面,重写了 getResolvableType 方法,在运行期动态的获取泛型对应的真正的对象类型,从而解决了编译阶段泛型擦除的问题。

很好,现在摸清楚了,是个很简单的思路,之前是 Spring 的,现在它是我的了。