我有一个使用Java 11的spring-boot项目。该项目依赖于redis,因此我在pom.xml中包含了spring-boot-starter-data-redis依赖关系。 spring-data-redis jar有一个名为JedisClientUtils的类,该类在类级别具有默认的访问修饰符。
当我使用mvn spring-boot:run运行该项目时,我收到JedisClientUtils类的错误NoClassDefFound错误。 在调试问题时,我发现在使用Java 8时同一项目成功运行。 我的pom.xml具有如下插件:
<build>
<finalName>${war.name}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
<dependencies>
<dependency>
<!-- update compiler plugin dependency on ASM for Java 11 compatibility -->
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>6.2</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
使用默认访问类构建Java 11项目是否还有其他要求? 日志以供参考:
java.lang.reflect.InvocationTargetException 在jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(本机方法)处 在jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 在jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 在java.lang.reflect.Method.invoke(Method.java:566) 在org.springframework.boot.maven.AbstractRunMojo $ LaunchRunner.run(AbstractRunMojo.java:558) 在java.lang.Thread.run(Thread.java:834)导致原因:java.lang.NoClassDefFoundError:无法初始化类 org.springframework.data.redis.connection.jedis.JedisClientUtils 在org.springframework.data.redis.connection.jedis.JedisConnection.isQueueing (JedisConnection.java:339)
spring-boot-data-redis版本:2.1.1发布 jedis.version:2.9.0
答案 0 :(得分:1)
您可以配置ForkJoinPool的线程工厂来设置您期望在应用程序范围内使用的类加载器,而不是每次任务运行时都设置上下文类加载器。请参阅CompletableFuture / ForkJoinPool Set Class Loader(不肮脏的答案)。
一个警告是,这一突破性变化似乎是由以下主张引起的:不能在应用程序生命周期的某个特定时刻依赖公共池初始化线程(例如,可以在类加载器初始化您的公共加载器之前初始化公共池线程)。春天的应用程序已创建)。
对于特定于Spring的应用程序,鉴于Java 9中引入的重大更改以及缺乏保证将使用classloader初始化公共池的保证,因此配置自定义ForkJoinPool并向其提交任务而不是依赖commonPool似乎是审慎的做法。即使使用定制工厂,您的spring应用程序也需要。像这样:
@Bean
public ForkJoinPool myForkJoinPool() {
int threads = Runtime.getRuntime().availableProcessors();
return new ForkJoinPool(threads, makeFactory("MyFactory"), null, false);
}
private ForkJoinWorkerThreadFactory makeFactory(String prefix) {
return pool -> {
final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
worker.setName(prefix + worker.getPoolIndex());
worker.setContextClassLoader(Application.class.getClassLoader());
return worker;
};
}
将您的池注入执行并行操作的组件中并被调用:
myForkJoinPool.submit(myTask)
答案 1 :(得分:0)
我遇到了类似的问题,但是您的问题中没有足够的细节来确保它是相同的。
但是,如果有帮助,这是我的经验:
ForkJoinPool
的实现在Java 8之后的某个地方进行了更改,它可能会影响您的某些代码。通常,在Java中创建新线程时,它要么在构造函数中使用特定的类加载器,要么重用父级的类加载器。 Java 8版本的ForkJoinPool
未为其线程指定类加载器(即使用了父级的线程),但是Java 11的ForkJoinPool
指定了系统类加载器。现在,当您尝试在ForkJoinPool
线程中的某个位置通过名称加载类而不指定正确的类加载器时(如JedisClientUtils一样),默认情况下它将使用系统类加载器,该系统不知道您的应用程序类。 CompletableFuture
和并行Streams
使用ForkJoinPool
是此问题的潜在要点。
这是一个失败的示例:
@Repository
public interface RedisRepo extends CrudRepository<Statistic, Long> {
}
@SpringBootApplication
public class Start {
...
public static void main(String[] args) {
new SpringApplication(Start.class).run(args);
CompletableFuture.runAsync(() -> {
redisRepo.deleteAll();
});
}
}
可能的解决方法:
public static void main(String[] args) {
new SpringApplication(Start.class).run(args);
CompletableFuture.runAsync(() -> {
ClassLoader contextClassLoaderBackup = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(Start.class.getClassLoader());
redisRepo.deleteAll();
} finally {
Thread.currentThread().setContextClassLoader(contextClassLoaderBackup);
}
});
}