Home > Software engineering >  How to load classes from a .war file and use them?
How to load classes from a .war file and use them?

Time:05-24

I have a project that is generating a .war file as a build artifact. I want to load this .war into the JVM so that I can parse the runtime annotations it contains, but I am having trouble with loading the classes.

I have the following snippet of code to fetch class names from a JarFile:

public static Set<String> getClassNamesFromJarFile(JarFile jarFile) throws IOException {
    Set<String> classNames = new HashSet<>();
    Enumeration<JarEntry> e = jarFile.entries();
    while (e.hasMoreElements()) {
        JarEntry jarEntry = e.nextElement();
        if (jarEntry.getName().endsWith(".class")) {
            String className = jarEntry.getName()
                    .replace("/", ".");
            className = className.substring(0, className.length() - 6);
            classNames.add(className);
        }
    }
    return classNames;
}

And I use this snippet for getting the JarFile:

protected static JarFile getJarFile(String jarFileUrl) throws IOException {
    try {
        return new JarFile(new URI(jarFileUrl.replaceAll(" ", " ")).getSchemeSpecificPart());
    } catch (URISyntaxException ex) {
        return new JarFile(jarFileUrl.substring("file:".length()));
    }
}

Then, on my main, I have the following code:

public static void main(String[] args) throws IOException {

    String warpath = "C:\\User\\Me\\Desktop\\someWarFile.war";

    // Fetch JarFile
    JarFile jar = getJarFile("file:"   warpath);

    // Load .war into the JVM
    URL url = new File(warpath).toURI().toURL();
    URLClassLoader child = new URLClassLoader(
            new URL[]{url},
            Object.class.getClassLoader()
    );

    // Get class names in the .war
    Set<String> classes = getClassNamesFromJarFile(jar);

    // Try to get classes from name
    for (String className : classes) {
        try {
            System.out.println("CLASS FOUND: "   className);
            Class<?> actualClass= Class.forName(className, true, child);
        } catch(Error | Exception e) {
            System.out.println("ERROR: "   e);
        }
    }
}

For every class I get the output:

...
CLASS FOUND: WEB-INF.classes.com.some.company.project.state.ProjectStateProvider$1
ERROR: java.lang.NoClassDefFoundError: com/some/company/project/state/ProjectStateProvider$1 (wrong name: WEB-INF/classes/com/some/company/project/state/ProjectStateProvider$1)
CLASS FOUND: WEB-INF.classes.com.some.company.configuration.rest.JsonProvider
ERROR: java.lang.NoClassDefFoundError: com/some/company/configuration/rest/JsonProvider (wrong name: WEB-INF/classes/com/some/company/configuration/rest/JsonProvider)
...

I have tried removing the WEB-INF. and the WEB-INF.classes. prefixes from the className, I have also tried adding .class to the className and every combination of the previous solutions, but I just get a java.lang.ClassNotFoundException.

How do I reference classes loaded from the .war like the WEB-INF.classes.com.some.company.project.state.ProjectStateProvider$1 class for example?

CodePudding user response:

I'm using this code, and it works fine:

private void reloadJar(final File pJarFile) throws InstantiationException, IllegalAccessException, MalformedURLException, IOException {
    try (final URLClassLoader classLoader = new URLClassLoader(new URL[] { pJarFile.toURI().toURL() }); //
            final JarFile file = new JarFile(pJarFile);) {
        
        final Plugin plugin = new Plugin(pJarFile, classLoader);
        final Enumeration<JarEntry> entries = file.entries();
        while (entries.hasMoreElements()) {
            final JarEntry jarEntry = entries.nextElement();
            if (!isClassFile(jarEntry)) {
                plugin.addNonClassfile(jarEntry);
                continue;
            }
            
            try {
                final String id = getClassName(jarEntry);
                final Class<?> cls = classLoader.loadClass(id);
                if (JcUClass.isSubclassOf(cls, KittenPluginIf.class)) {
                    final KittenPluginIf inst = (KittenPluginIf) cls.newInstance();
                    plugin.setPlugin(inst);
                } else if (JcUClass.isSubclassOf(cls, KittenPageIf.class)) {
                    final KittenPageIf inst = (KittenPageIf) cls.newInstance();
                    plugin.addPage(inst);
                } else {
                    plugin.addUtilityClass(jarEntry);
                }
            } catch (final ClassNotFoundException e2) {
                plugin.addClassNotFoundEntry(jarEntry);
            }
        }
        
        if (plugin.isValid()) mPlugins.add(plugin);
    }
}



static private boolean isClassFile(final ZipEntry entry) {
    return !entry.isDirectory() && entry.getName().endsWith(".class");
}
static private String getClassName(final ZipEntry entry) {
    final String className = entry.getName().replace('/', '.'); // including ".class"
    return className.substring(0, className.length() - ".class".length());
}

You have to remove some code that contains some custom classes of mine, but this outlines the procedure easily.

The other thing that you have to be aware of: those classes in the .jar or .war file have to be normal javac compiled classes, or any other builder that outputs default java class binaries, like EclipseBuild.

CodePudding user response:

OK. I've changed the getClassNamesFromJarFile method slightly:

public static Set<String> getClassNamesFromJarFile(
        JarFile file, String pfx, String sfx) {
    Set<String> result = new HashSet<>();
    Enumeration<JarEntry> enm = file.entries();
    while (enm.hasMoreElements()) {
        JarEntry e = enm.nextElement();
        String path = e.getName();
        if (path.startsWith(pfx) && path.endsWith(sfx)) {
            path = path.substring(pfx.length(), path.length()-sfx.length());
            path = path.replace('/', '.');
            result.add(path);
        }
    }
    return result;
}

Then:

public static void main(String[] args) throws IOException {

    String warpath = "C:\\User\\Me\\Desktop\\someWarFile.war";

    // Fetch JarFile
    JarFile jar = new JarFile(warpath);

    // Load .war into the JVM
    URL url = new URL("jar:file:"   warpath   "!/WEB-INF/classes/");
    URLClassLoader child = new URLClassLoader(
            new URL[]{url},
            Object.class.getClassLoader()
    );

    // Get class names in the .war
    Set<String> classes = getClassNamesFromJarFile(jar, "WEB-INF/classes/", ".class");

    // Try to get classes from name
    for (String className : classes) {
        try {
            System.out.println("CLASS FOUND: "   className);
            Class<?> actualClass= Class.forName(className, true, child);
        } catch(Error | Exception e) {
            System.out.println("ERROR: "   e);
        }
    }
}

UPDATE:

Now the classes will not load if they depend on other classes not in the classpath. For instance, if the war contains jar files in WEB-INF/lib, you need to add them to the URLClassLoader. You should also make available the jar of the servlet API.

  • Related