• 记一次springboot项目结合arthas排查ClassNotFoundException问题


    前言

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

    demo示例

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

    业务服务A

    1. package com.example.helloloader.service;
    2. import org.springframework.stereotype.Service;
    3. @Service
    4. public class HelloService {
    5. public String hello(){
    6. return "hello loader";
    7. }
    8. }
    9. 复制代码

    组件B

    1. @Component
    2. public class HelloServiceLoaderUtils implements ApplicationContextAware {
    3. private ApplicationContext applicationContext;
    4. public String invoke(String className){
    5. try {
    6. ClassLoader classLoader = ClassLoader.getSystemClassLoader();
    7. Class clz = classLoader.loadClass(className);
    8. Object bean = applicationContext.getBean(clz);
    9. Method method = clz.getMethod("hello");
    10. return (String) method.invoke(bean);
    11. } catch (Exception e) {
    12. e.printStackTrace();
    13. }
    14. return null;
    15. }
    16. @Override
    17. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    18. this.applicationContext = applicationContext;
    19. }
    20. }
    21. 复制代码

    服务A调用组件B

    1. @SpringBootApplication(scanBasePackages = "com.example")
    2. public class HelloLoaderApplication implements ApplicationRunner {
    3. @Autowired
    4. private HelloServiceLoaderUtils helloServiceLoaderUtils;
    5. public static void main(String[] args) {
    6. SpringApplication.run(HelloLoaderApplication.class, args);
    7. }
    8. @Override
    9. public void run(ApplicationArguments args) throws Exception {
    10. System.out.println(helloServiceLoaderUtils.invoke(HelloService.class.getName()));
    11. }
    12. }
    13. 复制代码

    异常复现

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

    1. hello loader
    2. 复制代码

    将业务服务A打包,通过

    1. java -jar hello-loader-0.0.1-SNAPSHOT.jar
    2. 复制代码

    启动访问

    出现了ClassNotFoundException异常

    异常排查

    class存在,却找不到class,要么就是类加载器错了,要么是class的位置错了。因此通过arthas进行排查。对arthas不了解的朋友,可以查看如下文章 java应用线上诊断神器--Arthas

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

    1. sc -d com.example.helloloader.service.HelloService
    2. 复制代码

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

    解决方法

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

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

    改好重新打包。此时重新运行,观察控制台

    1. 当前类加载器:org.springframework.boot.loader.LaunchedURLClassLoader@439f5b3d
    2. hello loader
    3. 复制代码

    2、方法二修改打包方式

    将打包插件由

    1. <build>
    2. <plugins>
    3. <plugin>
    4. <groupId>org.springframework.bootgroupId>
    5. <artifactId>spring-boot-maven-pluginartifactId>
    6. plugin>
    7. plugins>
    8. build>
    9. 复制代码

    切换成

    1. <build>
    2. <plugins>
    3. <plugin>
    4. <groupId>org.apache.maven.pluginsgroupId>
    5. <artifactId>maven-jar-pluginartifactId>
    6. <version>2.6version>
    7. <configuration>
    8. <archive>
    9. <manifest>
    10. <addClasspath>trueaddClasspath>
    11. <classpathPrefix>lib/classpathPrefix>
    12. <mainClass>com.example.helloloader.HelloLoaderApplicationmainClass>
    13. manifest>
    14. archive>
    15. configuration>
    16. plugin>
    17. <plugin>
    18. <groupId>org.apache.maven.pluginsgroupId>
    19. <artifactId>maven-dependency-pluginartifactId>
    20. <version>2.10version>
    21. <executions>
    22. <execution>
    23. <id>copyid>
    24. <phase>packagephase>
    25. <goals>
    26. <goal>copy-dependenciesgoal>
    27. goals>
    28. <configuration>
    29. <outputDirectory>
    30. ${project.build.directory}/lib
    31. outputDirectory>
    32. configuration>
    33. execution>
    34. executions>
    35. plugin>
    36. plugins>
    37. build>
    38. 复制代码

    切换打包方式后,重新运行

    1. 当前类加载器:sun.misc.Launcher$AppClassLoader@55f96302
    2. hello loader
    3. 复制代码

    此时正常输出,且加载器为AppClassLoader。我们可以通过

    1. sc -d com.example.helloloader.service.HelloService
    2. 复制代码

    观察HelloService的类加载器

    此时的HelloService的类加载器为AppClassLoader

    总结

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

    1. org.springframework.boot.loader.LaunchedURLClassLoader
    2. 复制代码

    2、arthas是个好东西,谁用谁知道

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

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


     

     

     

  • 相关阅读:
    十一、python实现单例模式
    探究ChatGPT与GPT-4的缺陷不足,揭示大预言LLM模型的局限性——没有完美的工具
    SpringBoot打包部署最佳实践
    不定积分第一类换元法(凑微分法)
    linux系统安装MySQL服务,详细图文教程
    touchGFX综合学习十三、基于cubeMX、正点原子H750开发版、RGB4.3寸屏移植touchGFX完整教程+工程(一)
    Cesium:WFS请求两种方式
    闭包问题优化
    北京化工大学数据结构2022/11/24作业 题解
    【408】计算机学科专业基础 - 操作系统
  • 原文地址:https://blog.csdn.net/m0_71777195/article/details/126247397