背景:
我正在开发一段java代码,该代码应该在tomcat上运行的供应商应用程序服务器中运行(带有startup.xml)。我无法触及供应商代码,但他们公开了一个框架,以便我们可以将插件放入他们的平台中,以创建他们公开的接口的自定义实现。作为版本升级的一部分,他们从 java 11 迁移到 java 17,我们正在经历回归(下面的错误)。
我们确保我们的 maven pom.xml 面向 java 17 并且 fastxml 库都是一致的...
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.13.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.13.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jaxb-annotations</artifactId>
<version>2.13.2</version>
</dependency>
我注意到在供应商的 tomcat lib 文件夹中,他们确实有 2.9.8 的非常旧的 Fasterxml 库,并且我注意到在该版本的 javadoc 中没有 TokenStreamFactory。不幸的是,我无法改变它,因为它是供应商代码/黑匣子。我只能在我创建的插件的范围内工作并放入其指定的文件夹中。我可以更改startup.xml,但这有点“我自己承担风险”。
ERROR: RuntimeException encountered attempting to send message.
java.lang.RuntimeException: org.apache.kafka.common.KafkaException: Failed to construct kafka producerException in thread "ordermessageadapter" java.lang.RuntimeException: org.apache.kafka.common.KafkaException: Failed to construct kafka producer
at com.vendor.xyz.conn.kafka.KafkaTopicSenderFactory.createAdapter(KafkaTopicSenderFactory.java:39)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at com.vendor.plugin.APIInvocationHandler.invoke(APIInvocationHandler.java:62)
at jdk.proxy4/jdk.proxy4.$Proxy166.createAdapter(Unknown Source)
at com.vendor.messageadapter.EndPoint$MySenderBufferThread.run(EndPoint.java:134)
Caused by: org.apache.kafka.common.KafkaException: Failed to construct kafka producer
at org.apache.kafka.clients.producer.KafkaProducer.<init>(KafkaProducer.java:442)
at org.apache.kafka.clients.producer.KafkaProducer.<init>(KafkaProducer.java:292)
at org.apache.kafka.clients.producer.KafkaProducer.<init>(KafkaProducer.java:319)
at org.apache.kafka.clients.producer.KafkaProducer.<init>(KafkaProducer.java:304)
at com.xyz.connection.manager.KafkaBuilder.producerCreation(KafkaBuilder.java:56)
at com.xyz.connection.manager.KafkaBuilder.connect(KafkaBuilder.java:52)
at com.vendor.xyz.conn.kafka.KafkaTopicSenderAdapter.<init>(KafkaTopicSenderAdapter.java:49)
at com.vendor.xyz.conn.kafka.KafkaTopicSenderFactory.createAdapter(KafkaTopicSenderFactory.java:36)
... 7 more
Caused by: java.lang.NoClassDefFoundError: Could not initialize class io.confluent.kafka.schemaregistry.client.rest.RestService
at io.confluent.kafka.schemaregistry.client.CachedSchemaRegistryClient.<init>(CachedSchemaRegistryClient.java:157)
at io.confluent.kafka.serializers.AbstractKafkaSchemaSerDe.configureClientProperties(AbstractKafkaSchemaSerDe.java:83)
at io.confluent.kafka.serializers.AbstractKafkaAvroSerializer.configure(AbstractKafkaAvroSerializer.java:56)
at io.confluent.kafka.serializers.KafkaAvroSerializer.configure(KafkaAvroSerializer.java:50)
at org.apache.kafka.clients.producer.KafkaProducer.<init>(KafkaProducer.java:378)
... 14 more
Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.VerifyError: Bad return type
Exception Details:
Location:
com/fasterxml/jackson/databind/cfg/MapperBuilder.streamFactory()Lcom/fasterxml/jackson/core/TokenStreamFactory; @7: areturn
Reason:
Type 'com/fasterxml/jackson/core/JsonFactory' (current frame, stack[0]) is not assignable to 'com/fasterxml/jackson/core/TokenStreamFactory' (from method signature)
Current Frame:
bci: @7
flags: { }
locals: { 'com/fasterxml/jackson/databind/cfg/MapperBuilder' }
stack: { 'com/fasterxml/jackson/core/JsonFactory' }
Bytecode:
0000000: 2ab4 0002 b600 08b0
[in thread "MyCode"]
at com.fasterxml.jackson.databind.json.JsonMapper.builder(JsonMapper.java:114)
at io.confluent.kafka.schemaregistry.utils.JacksonMapper.<clinit>(JacksonMapper.java:27)
at io.confluent.kafka.schemaregistry.client.rest.RestService.<clinit>(RestService.java:155)
... 19 more
我已经仔细检查过我的java插件版本除了2.13.2之外没有其他版本的fasterxml。关于如何解决或解决此问题的任何想法?我强烈地感觉到这与 tomcat 应用程序从 java 11 到 17 的变化有关......所以我希望也许我可以在startup.xml 中以某种方式调整它。我猜测当应用程序启动时,旧的 FasterXML 版本(2.9.8)会以某种方式产生干扰。
这看起来像是 “jar hell” 的经典案例,其中应用程序的不同部分及其依赖项需要同一库的不同版本。
在您的情况下,您的插件需要
fasterxml.jackson
版本 2.13.2
,但与 Tomcat 捆绑的供应商应用程序使用旧版本 (2.9.8
),导致兼容性问题和您遇到的运行时异常。类路径优先级决定加载旧版本的库而不是您的插件所需的版本。
Tomcat
└───lib
└───jackson-*-2.9.8 (Vendor libs, older version)
└───other vendor libs
Plugins
└───your-plugin.jar
└───jackson-*-2.13.2 (Your libs, required version)
如果可能,修改您的插件以使用自定义类加载器,该加载器优先考虑您的捆绑库而不是供应商应用程序提供的库。但这可能不受供应商框架的直接支持。
既然您提到您可以自行承担修改
startup.xml
的风险,您可以尝试调整类路径,以便在供应商的库之前更喜欢从插件的 jar 文件加载类。这可能涉及指定类加载器属性,例如插件上下文的 parentFirst="false"
。但是,这可能不可行或不可靠,具体取决于供应商应用程序和 Tomcat 如何管理类加载。
相反,您可以使用 Maven 的 Shade Plugin 创建插件的阴影(或超级)jar,它将特定版本的 Jackson 库重新定位到不同的包命名空间。这样,您的插件将使用其 Jackson 版本,而不会与供应商的版本发生冲突。然而,这需要仔细处理,以确保对重定位的类的all引用得到相应更新。
举个例子,您的
pom.xml
是:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<relocations>
<relocation>
<pattern>com.fasterxml.jackson</pattern>
<shadedPattern>your.custom.package.jackson</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
该设置会将所有
com.fasterxml.jackson
类重新定位到 your.custom.package.jackson
,允许您的插件使用其 Jackson 版本,而不会与供应商的版本冲突。您将更新代码以相应地使用新的包名称。
假设您已在
pom.xml
中配置了 Maven Shade 插件以将 com.fasterxml.jackson
重新定位到 com.mycompany.shaded.jackson
:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
public class Example {
public static void main(String[] args) {
ObjectMapper mapper = new ObjectMapper();
MyObject myObject = new MyObject();
try {
String json = mapper.writeValueAsString(myObject);
System.out.println(json);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
import com.mycompany.shaded.jackson.databind.ObjectMapper;
import com.mycompany.shaded.jackson.core.JsonProcessingException;
public class Example {
public static void main(String[] args) {
// Notice the import statements have changed to the shaded package
com.mycompany.shaded.jackson.databind.ObjectMapper mapper = new com.mycompany.shaded.jackson.databind.ObjectMapper();
MyObject myObject = new MyObject();
try {
String json = mapper.writeValueAsString(myObject);
System.out.println(json);
} catch (com.mycompany.shaded.jackson.core.JsonProcessingException e) {
e.printStackTrace();
}
}
}