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


    前言

    前阵子业务部门的项目出现了一个很奇怪的问题,有个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";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    组件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;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    服务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()));
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    异常复现

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

    hello loader
    
    • 1

    将业务服务A打包,通过

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

    启动访问

    出现了ClassNotFoundException异常

    异常排查

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

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

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


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

    解决方法

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

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

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

    当前类加载器:org.springframework.boot.loader.LaunchedURLClassLoader@439f5b3d
    hello loader
    
    • 1
    • 2

    2、方法二修改打包方式

    将打包插件由

    	<build>
    		<plugins>
    			<plugin>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-maven-plugin</artifactId>
    			</plugin>
    		</plugins>
    	</build>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    切换成

    	<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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

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

    当前类加载器:sun.misc.Launcher$AppClassLoader@55f96302
    hello loader
    
    • 1
    • 2

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

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

    观察HelloService的类加载器


    此时的HelloService的类加载器为AppClassLoader

    总结

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

    org.springframework.boot.loader.LaunchedURLClassLoader
    
    • 1

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

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

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

  • 相关阅读:
    流媒体传输 - RTSP Over HTTP
    轻量级ORM库peewee的基本使用
    CAPL学习之路-TLS函数
    linux(centOs7)部署mysql(8.0.20)数据库
    MP4 H.264 MPEG-4 MPEG-2
    2┃音视频直播系统之浏览器中通过 WebRTC 拍照片加滤镜并保存
    洛谷-1001-A+B问题
    python多进程
    java中lambda表达式之函数式接口
    平台H5对接支付宝支付接口(含分布式源码)
  • 原文地址:https://blog.csdn.net/kingwinstar/article/details/125482503