类加载器不一致导致的转换异常

最近在一个项目中使用rocketmq发送消息,在消费消息时,反序列化是出现一个报错,最开始以为是alibaba的fastjson报的错。

详细报错为:

java.lang.ClassCastException: com.dtyunxi.amdu.dms.common.event.clue.dto.PlatformClueMsgDto cannot be cast to com.dtyunxi.amdu.dms.common.event.clue.dto.PlatformClueMsgDto

at com.alibaba.fastjson.serializer.ASMSerializer_14_PlatformClueMsgDto.write(Unknown Source)

at com.alibaba.fastjson.serializer.JSONSerializer.writeWithFieldName(JSONSerializer.java:314)

at com.alibaba.fastjson.serializer.ASMSerializer_11_MessageVo.write(Unknown Source)

at com.alibaba.fastjson.serializer.JSONSerializer.write(JSONSerializer.java:285)

at com.alibaba.fastjson.JSON.toJSONString(JSON.java:731)

at com.alibaba.fastjson.JSON.toJSONString(JSON.java:669)

at com.alibaba.fastjson.JSON.toJSONString(JSON.java:634)

at com.dtyunxi.huieryun.mq.provider.rocket.RocketConsumer$1.consumeMessage(RocketConsumer.java:205)

at org.apache.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyService$ConsumeRequest.run(ConsumeMessageConcurrentlyService.java:411)

at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)

at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)

at java.util.concurrent.FutureTask.run(FutureTask.java)

at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)

从原文看是castException。检查包全路径,没有区别。初步以为是fastjson的bug。

后来通过类加载器,在consumer端查看本地加载的PlatformClueMsgDto,和消息体力的body消息体的加载器不一致,导致转换失败。

如下图

图1


图2

platformClueMsgDto是在本地加载的dto对象,而message.getData()也是platClueMsgDto(注:MessageVo是由第三方的jar包封装的对象)。

从截图看本地包的platformClueMsgDto的类加载器对象是RestartClassLoader,而MessageVo里的platClueMsgDto的类加载器是AppClassLoader。

一般情况下,我们的类加载器按理都应该是AppClassLoader。

所以我们来看看RestartCalssLoader类加载器是什么。

RestartCalssLoader是由Spring-boot-devtools引入的类加载器。

整个启动流程大概:

main() -> SpringApplication.run() -> SpringApplicationRunListeners.starting() ->

SimpleApplicationEventMulticaster.multicastEvent() RestartApplicationListener.onApplicationEvent() ->

RestartApplicationListener.onApplicationStartingEvent() -> Restarter.initialize()


由此我们看到是由实现ApplicationListener的RestartApplicationListener完成对Restarter的初始化的,而此是传入的Thread正是main线程,此线程的ContextClassLoader便是AppClassLoader,所以经过过滤后就将只有类似于 file:/projectPath/projectName/module/target/classes这样的属于此项目代码的URL,所以此类加载器不负责加载第三方jar包的类文件。

ClassLoader不一致的解决方法

1.最粗暴的方式就是直接去除依赖 Spring-boot-devtools

2.如果确实想要热部署功能,Springboot也提供了配置,没错就是上文提到的过滤URLs时使用DevToolsSettings读取META-INF/spring-devtools.properties配置文件进行纳入、排查,你可以自己新建 spring-devtools.properties 文件,配置上需要此自定义类加载器负责来加载的正则表达式,形式如(exclude表示排查、include表示纳入):

restart.exclude.spring-boot=/spring-boot/target/classes/

restart.exclude.spring-boot-devtools=/spring-boot-devtools/target/classes/

restart.exclude.spring-boot-starters=/spring-boot-starter-[\\w-]+/

restart.include.commons-pool2=/org/apache/commons/commons-pool2/2.4.3/commons-pool2-2.4.3.jar

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容