具有默认访问权限的类在Spring Boot Project for Java 11中在运行时导致NoClassDefFound错误

时间:2019-07-10 09:17:36

标签: maven spring-boot java-11

我有一个使用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

2 个答案:

答案 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);
        }
    });
}