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


linyb极客之路
336 声望193 粉丝