关于SpringCloudBus2的UnknownRemoteApplicationEvent异常,反序列化错误的问题记录及临时解决方案
在SpringCloudBus从1.3.4升级到2之后,如果你的事件继承了“RemoteApplicationEvent”,也就是自定义事件,如果这个事件是在你的jar包里,就能够正常反序列化,也就是从消息队列里将消息反序列化为对应的事件对象。但是自定义事件不在你的jar包,你也不需要监听这个事件,而是在其他服务中监听的,就会报下面的错误:
org.springframework.messaging.converter.MessageConversionException: Could not read JSON: Could not resolve type id 'TestEvent3' as a subtype of [simple type, class org.springframework.cloud.bus.event.RemoteApplicationEvent]: known type ids = [AckRemoteApplicationEvent, EnvironmentChangeRemoteApplicationEvent, RefreshRemoteApplicationEvent, TestEvent, UnknownRemoteApplicationEvent]
at [Source: (byte[])"{"type":"TestEvent3","timestamp":1610350332773,"originService":"ta-test:0:94c65f3099416ba7c5fb782a3c8440f0","destinationService":"","id":"cfd66a23-33fe-4bb0-a804-f053b1d9df94","message":"test3"}"; line: 1, column: 9]; nested exception is com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id 'TestEvent3' as a subtype of [simple type, class org.springframework.cloud.bus.event.RemoteApplicationEvent]: known type ids = [AckRemoteApplicationEvent, EnvironmentChangeRemoteApplicationEvent, RefreshRemoteApplicationEvent, TestEvent, UnknownRemoteApplicationEvent]
at [Source: (byte[])"{"type":"TestEvent3","timestamp":1610350332773,"originService":"ta-test:0:94c65f3099416ba7c5fb782a3c8440f0","destinationService":"","id":"cfd66a23-33fe-4bb0-a804-f053b1d9df94","message":"test3"}"; line: 1, column: 9]
at org.springframework.messaging.converter.MappingJackson2MessageConverter.convertFromInternal(MappingJackson2MessageConverter.java:235) ~[spring-messaging-5.0.10.RELEASE.jar:5.0.10.RELEASE]
at org.springframework.cloud.stream.converter.ApplicationJsonMessageMarshallingConverter.convertFromInternal(ApplicationJsonMessageMarshallingConverter.java:97) ~[spring-cloud-stream-2.0.1.RELEASE.jar:2.0.1.RELEASE]
at org.springframework.messaging.converter.AbstractMessageConverter.fromMessage(AbstractMessageConverter.java:181) ~[spring-messaging-5.0.10.RELEASE.jar:5.0.10.RELEASE]
at org.springframework.messaging.converter.CompositeMessageConverter.fromMessage(CompositeMessageConverter.java:70) ~[spring-messaging-5.0.10.RELEASE.jar:5.0.10.RELEASE]
at org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver.resolveArgument(PayloadArgumentResolver.java:137) ~[spring-messaging-5.0.10.RELEASE.jar:5.0.10.RELEASE]
at org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:116) ~[spring-messaging-5.0.10.RELEASE.jar:5.0.10.RELEASE]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:137) ~[spring-messaging-5.0.10.RELEASE.jar:5.0.10.RELEASE]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:109) ~[spring-messaging-5.0.10.RELEASE.jar:5.0.10.RELEASE]
at org.springframework.cloud.stream.binding.StreamListenerMessageHandler.handleRequestMessage(StreamListenerMessageHandler.java:55) ~[spring-cloud-stream-2.0.1.RELEASE.jar:2.0.1.RELEASE]
at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:109) ~[spring-integration-core-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:165) ~[spring-integration-core-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.integration.dispatcher.AbstractDispatcher.tryOptimizedDispatch(AbstractDispatcher.java:116) ~[spring-integration-core-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:132) ~[spring-integration-core-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:105) ~[spring-integration-core-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:73) ~[spring-integration-core-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:445) ~[spring-integration-core-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:394) ~[spring-integration-core-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:181) ~[spring-messaging-5.0.10.RELEASE.jar:5.0.10.RELEASE]
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:160) ~[spring-messaging-5.0.10.RELEASE.jar:5.0.10.RELEASE]
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:47) ~[spring-messaging-5.0.10.RELEASE.jar:5.0.10.RELEASE]
at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:108) ~[spring-messaging-5.0.10.RELEASE.jar:5.0.10.RELEASE]
at org.springframework.integration.endpoint.MessageProducerSupport.sendMessage(MessageProducerSupport.java:203) ~[spring-integration-core-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.integration.kafka.inbound.KafkaMessageDrivenChannelAdapter.access$300(KafkaMessageDrivenChannelAdapter.java:70) ~[spring-integration-kafka-3.0.3.RELEASE.jar:3.0.3.RELEASE]
at org.springframework.integration.kafka.inbound.KafkaMessageDrivenChannelAdapter$IntegrationRecordMessageListener.onMessage(KafkaMessageDrivenChannelAdapter.java:387) ~[spring-integration-kafka-3.0.3.RELEASE.jar:3.0.3.RELEASE]
at org.springframework.integration.kafka.inbound.KafkaMessageDrivenChannelAdapter$IntegrationRecordMessageListener.onMessage(KafkaMessageDrivenChannelAdapter.java:364) ~[spring-integration-kafka-3.0.3.RELEASE.jar:3.0.3.RELEASE]
at org.springframework.kafka.listener.adapter.RetryingMessageListenerAdapter.lambda$onMessage$0(RetryingMessageListenerAdapter.java:120) ~[spring-kafka-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:287) ~[spring-retry-1.2.2.RELEASE.jar:na]
at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:211) ~[spring-retry-1.2.2.RELEASE.jar:na]
at org.springframework.kafka.listener.adapter.RetryingMessageListenerAdapter.onMessage(RetryingMessageListenerAdapter.java:114) ~[spring-kafka-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.kafka.listener.adapter.RetryingMessageListenerAdapter.onMessage(RetryingMessageListenerAdapter.java:40) ~[spring-kafka-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeRecordListener(KafkaMessageListenerContainer.java:1077) [spring-kafka-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeWithRecords(KafkaMessageListenerContainer.java:1057) [spring-kafka-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeRecordListener(KafkaMessageListenerContainer.java:999) [spring-kafka-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeListener(KafkaMessageListenerContainer.java:867) [spring-kafka-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.run(KafkaMessageListenerContainer.java:725) [spring-kafka-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_221]
at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266) [na:1.8.0_221]
at java.util.concurrent.FutureTask.run(FutureTask.java) [na:1.8.0_221]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_221]
Caused by: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id 'TestEvent3' as a subtype of [simple type, class org.springframework.cloud.bus.event.RemoteApplicationEvent]: known type ids = [AckRemoteApplicationEvent, EnvironmentChangeRemoteApplicationEvent, RefreshRemoteApplicationEvent, TestEvent, UnknownRemoteApplicationEvent]
at [Source: (byte[])"{"type":"TestEvent3","timestamp":1610350332773,"originService":"ta-test:0:94c65f3099416ba7c5fb782a3c8440f0","destinationService":"**","id":"cfd66a23-33fe-4bb0-a804-f053b1d9df94","message":"test3"}"; line: 1, column: 9]
at com.fasterxml.jackson.databind.exc.InvalidTypeIdException.from(InvalidTypeIdException.java:43) ~[jackson-databind-2.9.7.jar:2.9.7]
at com.fasterxml.jackson.databind.DeserializationContext.invalidTypeIdException(DeserializationContext.java:1635) ~[jackson-databind-2.9.7.jar:2.9.7]
at com.fasterxml.jackson.databind.DeserializationContext.handleUnknownTypeId(DeserializationContext.java:1187) ~[jackson-databind-2.9.7.jar:2.9.7]
at com.fasterxml.jackson.databind.jsontype.impl.TypeDeserializerBase._handleUnknownTypeId(TypeDeserializerBase.java:291) ~[jackson-databind-2.9.7.jar:2.9.7]
at com.fasterxml.jackson.databind.jsontype.impl.TypeDeserializerBase._findDeserializer(TypeDeserializerBase.java:162) ~[jackson-databind-2.9.7.jar:2.9.7]
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedForId(AsPropertyTypeDeserializer.java:113) ~[jackson-databind-2.9.7.jar:2.9.7]
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:97) ~[jackson-databind-2.9.7.jar:2.9.7]
at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:254) ~[jackson-databind-2.9.7.jar:2.9.7]
at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:68) ~[jackson-databind-2.9.7.jar:2.9.7]
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013) ~[jackson-databind-2.9.7.jar:2.9.7]
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3121) ~[jackson-databind-2.9.7.jar:2.9.7]
at org.springframework.messaging.converter.MappingJackson2MessageConverter.convertFromInternal(MappingJackson2MessageConverter.java:222) ~[spring-messaging-5.0.10.RELEASE.jar:5.0.10.RELEASE]
... 38 common frames omitted
上面的错误描述得其实很明白了,就是说消息不能转换成“TestEvent3”,原因是没有找到这个类型,因为我们的服务中没有“TestEvent3”这个事件类,就转换不了,这里就觉得有点奇葩了,如果你其他服务有很多事件,那每发一次事件都会影响到其他服务,直接是污染了日志,一大堆报错一直弹出来就觉得很烦。当然如果你想简单处理就是把那个事件类引用进来咯,不过服务很多事件很多的话就要引用多个jar包,浪费资源还很麻烦。
于是我就抱着打破砂锅问到底的心态,直接是硬着头皮开始去读它的源码,我就顺着这个报错日志慢慢的深入,去寻找问题的根源。
首先是找到“MappingJackson2MessageConverter.convertFromInternal”这个方法,这个方法就是将消息反序列化成对应的类型:
protected Object convertFromInternal(Message<?> message, Class<?> targetClass, @Nullable Object conversionHint) {
JavaType javaType = getJavaType(targetClass, conversionHint);
Object payload = message.getPayload();
Class<?> view = getSerializationView(conversionHint);
// Note: in the view case, calling withType instead of forType for compatibility with Jackson <2.5
try {
if (payload instanceof byte[]) {
if (view != null) {
return this.objectMapper.readerWithView(view).forType(javaType).readValue((byte[]) payload);
}
else {
return this.objectMapper.readValue((byte[]) payload, javaType);
}
}
else {
if (view != null) {
return this.objectMapper.readerWithView(view).forType(javaType).readValue(payload.toString());
}
else {
return this.objectMapper.readValue(payload.toString(), javaType);
}
}
}
catch (IOException ex) {
throw new MessageConversionException(message, "Could not read JSON: " + ex.getMessage(), ex);
}
}
我们可以看到这里转换失败是直接抛异常出来了,所以异常是从这里抛出来了,那这个类型我们没有肯定就会转换失败呀,然后我就继续往上一层追,在“CompositeMessageConverter”这个类,发现消息转换的一个抽象类,都是由“MessageConverter”派生出一系列的转换器,这里就是直接选择实现了“SmartMessageConverter”接口的转换器来进行转换的:
public Object fromMessage(Message<?> message, Class<?> targetClass, @Nullable Object conversionHint) {
for (MessageConverter converter : getConverters()) {
Object result = (converter instanceof SmartMessageConverter ?
((SmartMessageConverter) converter).fromMessage(message, targetClass, conversionHint) :
converter.fromMessage(message, targetClass));
if (result != null) {
return result;
}
}
return null;
}
我在“CompositeMessageConverter”这个类找到了“converters”这个list,说明这里是管理converters的地方。
构造方法是初始化这些转换器,那就找找看哪里调用了这个方法,在“CompositeMessageConverterFactory”中就进行了初始化,我找到了这个方法:
private void initDefaultConverters() {
ApplicationJsonMessageMarshallingConverter applicationJsonConverter = new ApplicationJsonMessageMarshallingConverter(this.objectMapper);
applicationJsonConverter.setStrictContentTypeMatch(true);
this.converters.add(applicationJsonConverter);
this.converters.add(new TupleJsonMessageConverter(this.objectMapper));
this.converters.add(new ByteArrayMessageConverter());
this.converters.add(new ObjectStringMessageConverter());
// Deprecated converters
this.converters.add(new JavaSerializationMessageConverter());
this.converters.add(new KryoMessageConverter(null,true));
this.converters.add(new JsonUnmarshallingConverter(this.objectMapper));
}
这里添加了一系列的转换器,这时候想起Bus好像是有自己的转换器的,但是没有在这里见到,于是我就去1.3.4的版本看看都有哪些转换器。结果我在注入“CompositeMessageConverterFactory”这个Bean的时候,发现了“BusJacksonMessageConverter”,而正好这个类也实现了“SmartMessageConverter”接口,
这个转换器也是在SpringCloudBus包的,所以我可以断定bus就是用这个转换器来转换消息的!
@Override
public Object convertFromInternal(Message<?> message, Class<?> targetClass,
Object conversionHint) {
Object result = null;
try {
Object payload = message.getPayload();
if (payload instanceof byte[]) {
try {
result = this.mapper.readValue((byte[]) payload, targetClass);
} catch (InvalidTypeIdException e) {
return new UnknownRemoteApplicationEvent(new Object(), e.getTypeId(), (byte[]) payload);
}
} else if (payload instanceof String) {
try {
result = this.mapper.readValue((String) payload, targetClass);
} catch (InvalidTypeIdException e) {
return new UnknownRemoteApplicationEvent(new Object(), e.getTypeId(), ((String) payload).getBytes());
}
}
}
catch (Exception e) {
this.logger.error(e.getMessage(), e);
return null;
}
return result;
}
果然,从上面看到,如果转换失败,则会直接返回“UnknownRemoteApplicationEvent”,跟之前那个转换器处理的方式不一样,那个是直接抛出了异常,所以我们可以知道,问题就出在这里,我们没有加载到“BusJacksonMessageConverter”这个转换器,所以才会出现那个烦人的异常。那接下来的问题,就是要找出为什么会没有加载。。。
我发现“BusJacksonMessageConverter”这个转换器是从“customConverters”里加载过来的,也就是自定义的转换器(第三方),所以这个是Bus提供的转换器,那就找到加载“customConverters”的地方。1.3.4版本跟2之后的版本,加载“customConverters”的地方不一样,2是在“ContentTypeConfiguration”这个地方加载的,
这里的“customConverters”是null,那就说明了“BusJacksonMessageConverter”没注入进来,为什么会没注入进来呢。。。
于是我就在“BusJacksonMessageConverter”初始化方法那里打断点,发现“BusJacksonMessageConverter”是有注入的,但是却是在注入“customConverters”之后,这就有点离谱了,我“customConverters”都加载完了,你才注入进来,“CompositeMessageConverter”这里面就找不到我要的这个转换器了!于是我就继续往下看,寻找“BusJacksonMessageConverter”是在哪个地方注入的,最后是在“@RemoteApplicationEventScan”这个注解里看到关于“busJsonConverter”的“BeanDefinitionHolder”,由BeanDefinitionReaderUtils的registerBeanDefinition方法进行加载。
断点确实是有进来的,所以Bean是有加载,只是加载的顺序好像出了问题,这个问题我还没找到。不过既然是注入不到“BusJacksonMessageConverter”,那我们可以手动注入啊,于是我就拷贝了这个类,然后自己注入了这个Bean,结果问题就暂时解决了。
@Bean
@StreamMessageConverter
public MessageConverter customMessageConverter() {
BusJacksonMessageConverter busJacksonMessageConverter = new BusJacksonMessageConverter();
// 这里填的是事件的包扫描
busJacksonMessageConverter.setPackagesToScan(new String[]{"com.test.event"});
return busJacksonMessageConverter;
}
暂时就先写到这里啦,因为在社区上找不到问题的解决方案,所以只要自己去一点点地读源码,找到问题的根源,虽然说问题的根源还没找出来,不过也算是知道怎么解决了。有时间我会继续去追查下去,找到原因会继续分享出来的,总之这次读源码收获还是挺大的。