问题

插件发送jdq消息失败

异常:java.lang.RuntimeException: java.lang.ClassCastException: com.jd.bdp.jdw.avro.JdwData cannot be cast to com.jd.bdp.jdw.avro.JdwData

神机sdk发送Jdq消息代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Override
public void send(ShenJiMessage shenJiMessage) {
if (shenJiMessage.getText() == null) {
throw new IllegalArgumentException("消息内容不能为空");
}
try {
byte[] data = null;
if (MqMessageType.STRING.equals(messageType)) {
String text = (String)shenJiMessage.getText();
data = text.getBytes();
} else if (MqMessageType.JDW_DATA.equals(messageType)) {
// 在此行出现异常!!
JdwData text = (JdwData) shenJiMessage.getText();
data = serializer.toBytes(text);
}
producer.send(new ProducerRecord<>(shenJiMessage.getTopic(), new Bytes(data)), (metadata, exception) -> {});
} catch (Exception e) {
log.error("发送jdq消息异常", e);
throw new RuntimeException(e);
}
}

猜测类加载器shenJiMessage.getText()类加载器和声明JdwData类加载器不一致,分别打印下类加载器看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public void send(ShenJiMessage shenJiMessage) {
if (shenJiMessage.getText() == null) {
throw new IllegalArgumentException("消息内容不能为空");
}
try {
byte[] data = null;
if (MqMessageType.STRING.equals(messageType)) {
String text = (String)shenJiMessage.getText();
data = text.getBytes();
} else if (MqMessageType.JDW_DATA.equals(messageType)) {
JdwData jdwData = new JdwData();
ClassLoader classLoader = shenJiMessage.getText().getClass().getClassLoader();
ClassLoader classLoader1 = jdwData.getClass().getClassLoader();
log.info("send message, classLoader:{}, classLoader1:{}", classLoader, classLoader1);
JdwData text = (JdwData) shenJiMessage.getText();
data = serializer.toBytes(text);
}
producer.send(new ProducerRecord<>(shenJiMessage.getTopic(), new Bytes(data)), (metadata, exception) -> {});
} catch (Exception e) {
log.error("发送jdq消息异常", e);
throw new RuntimeException(e);
}
}

日志:

1
2023-12-13 15:03:49.928[INFO ]send message, classLoader:org.pf4j.PluginClassLoader@57e11696, classLoader1:sun.misc.Launcher$AppClassLoader@14dad5dc[com.jdl.shenji.sdk.framework.ioc.mq.jdq.ShenJiJdqProducer.send:42]

和猜想一致,shenJiMessage.getText()使用了插件类加载器,JdwData使用AppClassLoader。

StringUtils找不到compare方法

异常:java.lang.NoSuchMethodError: org.apache.commons.lang3.StringUtils.compare(Ljava/lang/String;Ljava/lang/String;)

神机类加载器加载规则

plugin_classloader01
plugin_classloader01
  1. 打破双亲委派机制:插件中的类会先由插件类加载器进行加载,如果判断插件类加载器加载不了再交给父类加载器(AppClassLoader)进行加载
  2. 插件类加载器findClass(String name)逻辑,根据类全限定名在插件路径下搜索是否有此类

问题分析

  1. 依据目前插件类加载的规则,我们认为是插件中引入了jdwdata、commons-lang3的依赖,但发现插件pom文件中对应的依赖的scope都是provided的,且在行云部署对应插件目录中却没有找到jdwdata、commons-lang3两个jar包
  2. 我们转换思路,是否插件中有和com.jd.bdp.jdw.avro.JdwData、org.apache.commons.lang3.StringUtils相同的类全限定名的类。发现有一个新引入的依赖rapp-common,打开jar包便可以发现端倪
plugin_classloader02
plugin_classloader02
plugin_classloader03
plugin_classloader03
  1. 结合类加载的规则com.jd.bdp.jdw.avro.JdwData是rapp-common中的类,而且是由PluginClassLoader加载的,所以出现了ClassCastException
  2. org.apache.commons.lang3.StringUtils是rapp-common中的类,方法中缺少了compare方法,所以出现NoSuchMethodError

问题解决

rapp-common这个包是有问题的,重写了大量第三方类且使用了相同的包路径,如果项目中还使用了例如jdwdata、commons-lang3这些包,就会出现类冲突等问题。

插件中使用rapp-common,目的是使用com.jdl.rapp.common.util.CharSequenceMapUtils#stringMap2SequenceMap工具方法将Map<String, String>转换为Map<CharSequence, CharSequence>。那么可以将类CharSequenceMapUtils复制插件中,不引入rapp-common。

这样com.jd.bdp.jdw.avro.JdwData会交由AppClassLoader加载,org.apache.commons.lang3.StringUtils就路由到commons-lang3包中正确代码。