How to loadClass spring class from spring jar

99 views Asked by At

I got the following classes in a spring project

interface IStep {
    IStep doStep();
    String getName();
}

@Component
public class Step1 implements IStep {
    public final static String NAME = "STEP1";

    ...
}

@Component
public class Step1 implements IStep {
    ...
}

A jar is built from this project.

Using a plain simple java project, i'm trying to load the jar to analyse the dependencies between classes and create some diagrams. My first use case is to find all the classes that implement IStep and get their name.

I'm trying to use reflection to load the jar, get the classes and load them. However, I keep getting a noclassdeffounderror on Step1. How can I load the class?

JarFile jarFile = new JarFile(pathToJar);
Enumeration<JarEntry> e = jarFile.entries();

URL[] urls = { new URL("jar:file:" + pathToJar+"!/") };
URLClassLoader cl = URLClassLoader.newInstance(urls);

while (e.hasMoreElements()) {
    JarEntry je = e.nextElement();
    if(je.isDirectory() || !je.getName().endsWith(".class") || !je.getName().contains("mypackagename")){
        continue;
    }
    // -6 because of .class
    String className = je.getName().substring(0,je.getName().length()-6);
    className = className.replace('/', '.');
    Class c = cl.loadClass(className); //noclassdeffounderror
}
2

There are 2 answers

0
tesmo On BEST ANSWER

The spring boot plugin generates a unique structure by default: See the Spring docs for more details.

You will need

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-loader</artifactId>
</dependency>

Then you can use the Spring tooling to do something like this

File myJarFile = new File("./myspringbootdep.jar");
NestedJarFile jarFile = new NestedJarFile(myJarFile, "BOOT-INF/classes/");
Enumeration<JarEntry> e = jarFile.entries();
ArrayList<JarEntry> jarEntries = Collections.list(e);

//This is required to make URLs with `nested:` work
org.springframework.boot.loader.net.protocol.Handlers.register();

URL[] urls = { JarUrl.create(myJarFile, "BOOT-INF/classes/") };

LaunchedClassLoader launchedClassLoader = new LaunchedClassLoader(false, urls, this.getClass().getClassLoader());


for(JarEntry je : jarEntries) {

            System.out.println(je.getName());


            if(je.isDirectory() || !je.getName().endsWith(".class") || !je.getName().contains("example")){
                continue;
            }

            //Replaces slashes with dots and remove .class
            Class c = launchedClassLoader.loadClass(je.getName().replaceAll("/", ".").replaceFirst("\\.class$", ""));

            System.out.println(c);
}
1
AndrewL On

add something like System.out.println(je.getName()); after the if expression and show the output please?

@AndrewL I get something like this: BOOT-INF.classes.com.example.demo.Step1

So the problem is about the pathing inside the jar file.

Try this:

String className = je.getName() .replace("BOOT-INF.classes.", "") .substring(0, je.getName().length() - 6);

I tried that, but that gives me a ClassNotFoundException.

Ah, now I am understanding! I didn't read clearly beforehand.

Yes, you'll get ClassNotFoundException if the class cannot be found in the jar file (or file system). But if there is a dependency problem you'll get the similarly named NoClassDefFoundError.

The problem you are have is that it seems your Jar file was created by Spring Boot in its special Executable Jar File Structure. This locates dependencies in layered way inside the jar file.

A simple standard URLClassLoader won't know how to find the dependencies that are in the embedded jar files, and this is why you are getting a NoClassDefFoundError.

Either don't package your project using Spring Boot's layered index consider alternatives like:

If this is not an option you will need delve into the tooling Spring Boot provides to read its Jar file structure (see section 2.1 of Executable Jar File Structure)