foreword
A very strange problem occurred in the project of the business department a while ago. There is a class that clearly exists, and the local idea runs fine. Then, a ClassNotFoundException problem occurs as soon as it is released online, and the class does exist online. This article uses a demo example to reproduce such a situation
demo example
Note: The project framework of this article is springboot2. This article only demonstrates the contents of ClassNotFoundException and does not simulate the business flow
business service A
package com.example.helloloader.service;
import org.springframework.stereotype.Service;
@Service
public class HelloService {
public String hello(){
return "hello loader";
}
}
component 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;
}
}
Service A calls component 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()));
}
}
Abnormal recurrence
If the call is made through the local idea, the console will print out normally
hello loader
Package business service A through
java -jar hello-loader-0.0.1-SNAPSHOT.jar
Initiate access
A ClassNotFoundException occurred
Troubleshooting
The class exists, but the class is not found, either the class loader is wrong, or the class is in the wrong location. Therefore, it is checked through arthas. For friends who don't know arthas, you can check the following articles
Java application online diagnostic artifact--Arthas
We view the com.example.helloloader.service.HelloService loader with the following command
sc -d com.example.helloloader.service.HelloService
It can be seen from the picture that the class loader of the packaged HelloService is the loader packaged by spring, so the HelloService cannot be loaded with appClassLoader
Solution
1. Method 1: Change the appClassLoader to the spring packaged loader
The way to do this is to change ClassLoader.getSystemClassLoader() to
Thread.currentThread().getContextClassLoader() can
Changed and repackaged. Rerun at this point and watch the console
当前类加载器:org.springframework.boot.loader.LaunchedURLClassLoader@439f5b3d
hello loader
2. Method 2: Modify the packaging method
will package the plugin by
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
switch to
<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>
After switching the packaging method, re-run
当前类加载器:sun.misc.Launcher$AppClassLoader@55f96302
hello loader
At this time, the output is normal, and the loader is AppClassLoader. we can pass
sc -d com.example.helloloader.service.HelloService
Observe the classloader of HelloService
At this time, the class loader of HelloService is AppClassLoader
Summarize
1. If the project uses the springboot packaging plug-in, its class will be placed in /BOOT-INF, and the class loader in this directory is
org.springframework.boot.loader.LaunchedURLClassLoader
2, arthas is a good thing, who knows who uses it
3. At the time of business investigation, the process was a little more complicated than the example in my article. Because the project is deployed to k8s, when there is no problem with the startup of the local project, the business side's research and development has always focused on the problem in k8s, and has always thought that it was a problem caused by k8s.
Later, the business side found me and asked me to help with the investigation. My first reaction was that there might be a problem with the packaging, so I asked the business side to make a package and try it locally with java -jar, but the business side's research and development said with certainty , he tried to run jar locally and there is no problem. Because I don't have permission to access the code of the business side, there is no way to verify it. Later, I can only suggest that they install arthas, and finally solve the problem with arthas
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。