前言
前阵子业务部门的我的项目呈现了一个很奇怪的问题,有个class明明存在,本地idea运行也没问题,而后一公布线上就呈现ClassNotFoundException问题,而且线上这个class的确是存在的。本文就通过一个demo示例来复现这么一个状况
demo示例
注: 本文的我的项目框架为springboot2。本文仅演示ClassNotFoundException相干内容,并不模仿业务流
业务服务A
package com.example.helloloader.service;import org.springframework.stereotype.Service;@Servicepublic class HelloService { public String hello(){ return "hello loader"; }}
组件B
@Componentpublic 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@439f5b3dhello 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@55f96302hello 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解决了问题