最近在一个项目中使用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 图2platformClueMsgDto是在本地加载的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
网友评论