帮忙检查为什么会 java.lang.ClassNotFoundException?

我的 springboot jar 项目,采用了 thin 模式的打包方式,直接在 IDEA 中运行没有问题,但是打包为 jar 后在本地运行就报错:

Invalid value 'com.cc.visitor.config.KaptchaTextCreator' for config parameter 'kaptcha.textproducer.impl'.] with root cause
java.lang.ClassNotFoundException: com.cc.visitor.config.KaptchaTextCreator

下面介绍源码中我认为相关的配置,请帮忙排查为什么会没有找到类 KaptchaTextCreator

  1. 制作了配置类 SystemConfig,源码如下
@Configuration
public class SystemConfig {

    /**
     * 2024年10月10日 15:11:43 依赖 com.github.penggle.kaptcha
     * 的验证码工具配置
     * @return
     */
    @Bean("kaptcha")
    public DefaultKaptcha getDefaultKaptcha(){
        com.google.code.kaptcha.impl.DefaultKaptcha defaultKaptcha = new com.google.code.kaptcha.impl.DefaultKaptcha();
        Properties properties = new Properties();
        properties.put("kaptcha.border", "no");
        properties.put("kaptcha.textproducer.font.color", "red");
        properties.put("kaptcha.image.width", "170");
        properties.put("kaptcha.image.height", "65");
        properties.put("kaptcha.textproducer.font.size", "45");
        properties.put("kaptcha.session.key", "verifyCode");
        properties.put("kaptcha.textproducer.char.space", "6");
        properties.put("kaptcha.obscurificator.impl", "com.google.code.kaptcha.impl.WaterRipple");
        // properties.put("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
        properties.put("kaptcha.background.clear.from", "yellow");
        properties.put("kaptcha.background.clear.to", "green");

        properties.put("kaptcha.textproducer.char.length", "4");
        properties.put("kaptcha.textproducer.impl", "com.cc.visitor.config.KaptchaTextCreator");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}
  1. 在类 SystemConfig 的同目录下有类 KaptchaTextCreator,源码如下
public class KaptchaTextCreator extends DefaultTextCreator {
    private static final String[] NUMBER= "0,1,2,3,4,5,6,7,8,9,10".split(",");

    @Override
    public String getText() {
        Integer result = 0;//结果
        Random random = new Random();
        int x = random.nextInt(10);
        int y = random.nextInt(10);
        StringBuilder chinese = new StringBuilder();
        int randomop = (int) random.nextInt(4);
        //判断结果生成加减乘除
        switch (randomop){
            case 0 :
                result = x * y;
                chinese.append(NUMBER[x]);
                chinese.append("*");
                chinese.append(NUMBER[y]);
                break;
            case 1 :
                if (x == 0 && y % x == 0) {
                    result = y / x;
                    chinese.append(NUMBER[y]);
                    chinese.append("/");
                    chinese.append(NUMBER[x]);
                } else {
                    result = x + y;
                    chinese.append(NUMBER[x]);
                    chinese.append("+");
                    chinese.append(NUMBER[y]);
                }
                break;
            case 2 :
                if (x >= y) {
                    result = x - y;
                    chinese.append(NUMBER[x]);
                    chinese.append("-");
                    chinese.append(NUMBER[y]);
                } else {
                    result = y - x;
                    chinese.append(NUMBER[y]);
                    chinese.append("-");
                    chinese.append(NUMBER[x]);
                }
                break;
            default:
                result = x + y;
                chinese.append(NUMBER[x]);
                chinese.append("+");
                chinese.append(NUMBER[y]);
        }
        //拼接结果返回
        chinese.append("=?" + result);
        //chinese.append("=?");
        return chinese.toString();
    }
}
  1. 项目 pom.xml 的完整源码如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.cc</groupId>
    <artifactId>visitor</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>visitor</name>
    <description>visitor</description>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.6.13</spring-boot.version>
        <com.alibaba.druid.version>1.2.16</com.alibaba.druid.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>org.mybatis.spring.boot</groupId>-->
<!--            <artifactId>mybatis-spring-boot-starter</artifactId>-->
<!--            <version>2.2.2</version>-->
<!--        </dependency>-->

<!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-devtools</artifactId>-->
<!--            <scope>runtime</scope>-->
<!--            <optional>true</optional>-->
<!--        </dependency>-->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- 下面是手动添加的依赖 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${com.alibaba.druid.version}</version>
        </dependency>
        <dependency>
            <groupId>com.mybatis-flex</groupId>
            <artifactId>mybatis-flex-spring-boot-starter</artifactId>
            <version>1.7.5</version>
        </dependency>
        <dependency>
            <groupId>com.cc</groupId>
            <artifactId>cc-alltype</artifactId>
            <version>1.0</version>
            <scope>system</scope>
            <systemPath>${project.basedir}/lib/cc-alltype.jar</systemPath>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- 图形验证码 -->
        <dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <version>2.3.2</version>
        </dependency>
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>com.thoughtworks.xstream</groupId>
            <artifactId>xstream</artifactId>
            <version>1.3.1</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <finalName>visitor.new</finalName>
        <plugins>
            <!--1、编译出不带lib文件夹的Jar包-->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.6.13</version>
                <configuration>
                    <!--表示编译版本配置有效
                    <fork>true</fork> -->
                    <!--引入第三方jar包时,不添加则引入的第三方jar不会被打入jar包中-->
                    <includeSystemScope>true</includeSystemScope>
                    <!--排除第三方jar文件-->
                    <includes>
                        <include>
                            <groupId>nothing</groupId>
                            <artifactId>nothing</artifactId>
                        </include>
                        <include>
                            <groupId>com.cc</groupId>
                            <artifactId>cc-alltype</artifactId>
                        </include>
                    </includes>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <!--2、完成对Java代码的编译,可以指定项目源码的jdk版本,编译后的jdk版本,以及编码-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <!--源代码使用的JDK版本-->
                    <source>${java.version}</source>
                    <!--需要生成的目标class文件的编译版本-->
                    <target>${java.version}</target>
                    <!--字符集编码-->
                    <encoding>UTF-8</encoding>
                    <!--用来传递编译器自身不包含但是却支持的参数选项-->
                    <compilerArguments>
                        <verbose/>
                        <!--windwos环境(二选一)-->
                        <bootclasspath>${env.JAVA_HOME}/jre/lib/rt.jar;${env.JAVA_HOME}/jre/lib/jce.jar</bootclasspath>
                        <!--Linux环境(二选一)
                        <bootclasspath>${java.home}/lib/rt.jar:${java.home}/lib/jce.jar</bootclasspath>-->
                    </compilerArguments>
                </configuration>
            </plugin>

            <!--3、将所有依赖的jar文件复制到target/lib目录-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <!--复制到哪个路径,${project.build.directory}缺醒为target,其他内置参数见下面解释-->
                            <outputDirectory>${project.build.directory}/lib</outputDirectory>
                            <overWriteReleases>false</overWriteReleases>
                            <overWriteSnapshots>false</overWriteSnapshots>
                            <overWriteIfNewer>true</overWriteIfNewer>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <!--4、指定启动类,指定配置文件,将依赖打成外部jar包-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <!--是否要把第三方jar加入到类构建路径-->
                            <addClasspath>true</addClasspath>
                            <!--外部依赖jar包的最终位置-->
                            <classpathPrefix>lib/</classpathPrefix>
                            <!--项目启动类-->
                            <mainClass>com.cc.visitor.VisitorApplication</mainClass>
                        </manifest>
                    </archive>
                    <!--资源文件不打进jar包中,做到配置跟项目分离的效果-->
                    <excludes>
                        <!--业务jar中过滤application.properties/yml文件,在jar包外控制-->
                        <exclude>*.properties</exclude>
                        <exclude>*.xml</exclude>
                        <exclude>*.yml</exclude>
                    </excludes>
                </configuration>
            </plugin>

        </plugins>
    </build>

</project>
  1. 项目中使用了我自己制作的一个工具 jarsrc 的兄弟目录 lib 下有文件 cc-alltype.jar
  2. 通过下图的2个步骤编译 jar
    image.png
  3. 编译后在项目的 target 目录下有文件 visitor.new.jarlib 目录
  4. 将文件 visitor.new.jarlib 目录拷贝到另外一个用于部署项目的目录下,如下图:
    image.png
  5. 其中的 start.bat 用于启动项目,代码是:
@echo off
start javaw -jar visitor.jar --spring.config.location="D:/projs/visitor/backend/application.yml" -Xms512m -Xmx512m -Dfile.encoding=UTF-8 -Dloader.path="D:/projs/visitor/backend/lib/"
exit
  1. 当访问请求验证码的页面时日志文件中出现报错:
ERROR o.a.c.c.C.[.[.[/visitorbe].[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [/visitorbe] threw exception [Request processing failed; nested exception is com.google.code.kaptcha.util.ConfigException: Invalid value 'com.cc.visitor.config.KaptchaTextCreator' for config parameter 'kaptcha.textproducer.impl'.] with root cause
java.lang.ClassNotFoundException: com.cc.visitor.config.KaptchaTextCreator
  1. 我通过 360 压缩工具打开 visitor.jar 后看到在路径 visitor.jar\BOOT-INF\classes\com\cc\visitor\config 下有文件 KaptchaTextCreator.class。为什么还会报没有找到这个类呢?

不知是否列举完全了,请帮忙解决,万分感谢!

阅读 415
avatarAI BotBETA

解决方案

问题出在你的 Spring Boot 项目使用了 thin jar 打包方式,并且你通过 -Dloader.path 参数指定了外部依赖库路径。在使用 spring-boot-thin-launcher 或类似机制时,类加载器的工作方式会有所不同,特别是在处理外部依赖和内部 JAR 文件时。

根本原因

  1. 类加载器问题:当你使用 thin 模式打包 Spring Boot 应用时,应用本身可能不包含所有依赖的类。这些依赖通常通过 -Dloader.path 指定的路径加载。但是,如果你的 KaptchaTextCreator 类在应用的内部 JAR(即 visitor.jar)中,而该 JAR 又被 thin launcher 管理,那么类加载器可能不会正确地从内部 JAR 中加载这个类,特别是当外部路径也被用于类加载时。
  2. 路径和包名:尽管你确认 KaptchaTextCreator.class 在正确的路径下(visitor.jar\BOOT-INF\classes\com\cc\visitor\config),类加载器可能因为配置不当或优先级问题而无法找到它。

解决方案步骤

  1. 检查类加载器配置

    • 确保 -Dloader.path 指向的目录包含所有必要的依赖,但不包括应用自身的类(这些应该由 thin launcher 自动从内部 JAR 加载)。
    • 如果可能,尝试移除或调整 -Dloader.path,看看是否仍然需要它,或者是否可以通过其他方式(如直接在 JAR 中包含所有依赖)来解决问题。
  2. 调整打包方式

    • 考虑不使用 thin 模式打包,而是使用标准的 Spring Boot 可执行 JAR,这样所有必要的类都会包含在 JAR 中,无需外部依赖路径。
    • 如果必须使用 thin 模式,确保所有自定义类(如 KaptchaTextCreator)都正确配置,并且类加载器配置不会干扰它们的加载。
  3. 检查 Spring Boot 版本和依赖

    • 确保你使用的 Spring Boot 版本与 spring-boot-thin-launcher 或其他相关插件兼容。
    • 检查 pom.xml 中的依赖是否完整,特别是与 kaptcha 相关的依赖。
  4. 调试和日志

    • 增加日志级别以获取更多关于类加载失败的信息。
    • 使用 JVM 的 -verbose:class 参数来跟踪类加载过程,这可以帮助你确定类加载器是否尝试加载 KaptchaTextCreator 以及从哪里加载。
  5. 重新构建和部署

    • 在做了上述更改后,重新构建你的 JAR 文件并部署到测试环境中。
    • 确保所有路径和配置都是最新的,并且与你的更改相匹配。

通过上述步骤,你应该能够诊断并解决问题,使 KaptchaTextCreator 类能够被正确加载,从而避免 ClassNotFoundException

1 个回答

你的问题可以参考这个排查
java.lang.ClassNotFoundException 全解析与解决方案

类路径配置错误
依赖冲突或缺失
JAR 文件未加载
编译与运行环境不一致

pom.xml
添加以下代码

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <includeSystemScope>true</includeSystemScope>
            </configuration>
        </plugin>
    </plugins>
</build>
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏