关于springboot:记一次springboot项目结合arthas排查ClassNotFoundException问题

2次阅读

共计 3461 个字符,预计需要花费 9 分钟才能阅读完成。

前言

前阵子业务部门的我的项目呈现了一个很奇怪的问题,有个 class 明明存在,本地 idea 运行也没问题,而后一公布线上就呈现 ClassNotFoundException 问题,而且线上这个 class 的确是存在的。本文就通过一个 demo 示例来复现这么一个状况

demo 示例

注: 本文的我的项目框架为 springboot2。本文仅演示 ClassNotFoundException 相干内容,并不模仿业务流

业务服务 A

package com.example.helloloader.service;

import org.springframework.stereotype.Service;

@Service
public class HelloService {public String hello(){return "hello loader";}
}

组件 B

@Component
public class HelloServiceLoaderUtils implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public String invoke(String className){
        try {ClassLoader classLoader = ClassLoader.getSystemClassLoader();
            Class clz = classLoader.loadClass(className);
            Object bean = applicationContext.getBean(clz);
            Method method = clz.getMethod("hello");
            return (String) method.invoke(bean);

        } catch (Exception e) {e.printStackTrace();
        }

        return null;
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}
}

服务 A 调用组件 B

@SpringBootApplication(scanBasePackages = "com.example")
public class HelloLoaderApplication implements ApplicationRunner {

    @Autowired
    private HelloServiceLoaderUtils helloServiceLoaderUtils;

    public static void main(String[] args) {SpringApplication.run(HelloLoaderApplication.class, args);
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {System.out.println(helloServiceLoaderUtils.invoke(HelloService.class.getName()));
    }
}

异样复现

如果通过本地 idea 进行调用,控制台会失常打印出

hello loader

将业务服务 A 打包,通过

java -jar hello-loader-0.0.1-SNAPSHOT.jar

启动拜访

呈现了 ClassNotFoundException 异样

异样排查

class 存在,却找不到 class,要么就是类加载器错了,要么是 class 的地位错了。因而通过 arthas 进行排查。对 arthas 不理解的敌人,能够查看如下文章
java 利用线上诊断神器 –Arthas

咱们通过如下命令查看 com.example.helloloader.service.HelloService 加载器

 sc -d com.example.helloloader.service.HelloService

从图片能够看出打包后的 HelloService 的类加载器为 spring 封装过的加载器,因而用 appClassLoader 是加载不到 HelloService

解决办法

1、办法一将 appClassLoader 改成 spring 封装的加载器

做法就是将 ClassLoader.getSystemClassLoader()改成
Thread.currentThread().getContextClassLoader() 即可

改好从新打包。此时从新运行,察看控制台

以后类加载器:org.springframework.boot.loader.LaunchedURLClassLoader@439f5b3d
hello loader

2、办法二批改打包形式

将打包插件由

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

切换成

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                            <mainClass>com.example.helloloader.HelloLoaderApplication</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.10</version>
                <executions>
                    <execution>
                        <id>copy</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>
                                ${project.build.directory}/lib
                            </outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

切换打包形式后,从新运行

以后类加载器:sun.misc.Launcher$AppClassLoader@55f96302
hello loader

此时失常输入,且加载器为 AppClassLoader。咱们能够通过

sc -d com.example.helloloader.service.HelloService

察看 HelloService 的类加载器

此时的 HelloService 的类加载器为 AppClassLoader

总结

1、如果我的项目采纳 springboot 的打包插件,他的 class 会放在 /BOOT-INF,且该目录下的 class 类加载器为

org.springframework.boot.loader.LaunchedURLClassLoader

2、arthas 是个好货色,谁用谁晓得

3、过后业务排查的时候,过程是比我文章示例还要简单一点。因为我的项目是部署到 k8s 中,当本地我的项目启动没问题时,业务方的研发就始终把问题聚焦在 k8s 中,始终感觉是 k8s 引发的问题。

前面他们业务方找到我,叫我帮忙排查,我第一反馈就是可能打包出了问题,于是我让业务方打个包,本地以 java -jar 试下,但业务方的研发又很必定的说,他试过本地打 jar 运行也没问题。因为业务方的代码我这边是没权限拜访的,没方法进行验证。前面只能倡议他们装置 arthas,最终联合 arthas 解决了问题

正文完
 0