1

I need to read a Spring Boot jar and load all the clases on a ClassLoader.

My problem,in spring boot classes are on "/BOOT-INF/classes" directory and not on the root directory.

Anybody knows how i can load this classes in my ClassLoader?

I try to do this:

private URLClassLoader getURLClassLoaderFromJar(Path jarPath) throws MalformedURLException {
        return URLClassLoader
                .newInstance(new URL[] { new URL("jar:file:" + jarPath.toAbsolutePath().toString() + "!/") });
}

This load the jar, but no the classes inside /BOOT-INF/classes

Ildelian
  • 1,278
  • 5
  • 15
  • 38
  • Edited adding info. – Ildelian Feb 12 '18 at 11:56
  • try this https://stackoverflow.com/a/11016392/1724816 – yvoytovych Feb 12 '18 at 12:08
  • Thanks, I am trying this at this moment. Only work with classes on the root. If the class are inside a directory throws a ClassNotFound Exception – Ildelian Feb 12 '18 at 12:16
  • in case your *.class dont start from jar root just specify internal path (in this case spring-boot jar uses "!/BOOT-INF/classes" ): ```new URL("jar:file:" + jarPath.toAbsolutePath().toString() + "!/BOOT-INF/classes")``` – ezer Feb 08 '22 at 16:31

2 Answers2

2

Investigating a bit how spring boot loads third party(Liquibase) classes, i would go like this:

Given that you know the package name you want to load

Resource[] scan(ClassLoader loader, String packageName) throws IOException {
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(
                loader);
        String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                + ClassUtils.convertClassNameToResourcePath(packageName) + "/**/*.class";
        Resource[] resources = resolver.getResources(pattern);
        return resources;
    }


void findAllClasses(String packageName, ClassLoader loader) throws ClassNotFoundException {
        MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(
                loader);
        try {
            Resource[] resources = scan(loader, packageName);
            for (Resource resource : resources) {
                MetadataReader reader = metadataReaderFactory.getMetadataReader(resource);
                ClassUtils.forName(reader.getClassMetadata().getClassName(), loader)
            }
        } catch (IOException ex) {
            throw new IllegalStateException(ex);
        }
    }

Also use your class loader with loaded jar:

findAllClasses("com.package", getURLClassLoaderFromJar(pathToJar));

This variant is safe to use with Spring Boot packaged executable JARs

yvoytovych
  • 871
  • 4
  • 12
0

I finally opted for decompress de jar on a temporary directory and create a URLClassloader with this entries: One to the root directory. One to the BOOT-INF/classes And one for every jar in BOOT-INT/lib

Path warDirectory = decompressWar(absolutePathFile);

File rootDir = new File(warDirectory.toAbsolutePath().toString());
File springBootDir = new File(warDirectory.toAbsolutePath().toString() + "/BOOT-INF/classes/");

List<URL> listaURL = new ArrayList<URL>();
listaURL.add(rootDir.toURI().toURL());
listaURL.add(springBootDir.toURI().toURL());

//This scan the BOOT-INF/lib folder and return a List<URL> with all the libraries.
listaURL.addAll(getURLfromSpringBootJar(warDirectory));


URL[] urls = new URL[listaURL.size()];
urls = listaURL.toArray(urls);
cl = new URLClassLoader(urls);

//This explore the JAR and load all the .class fulies to get the className.
resultClassesBean = loadJars(Collections.singletonList(pathJarFile), cl);

if(resultClassesBean != null && resultClassesBean.getListResultClasses() != null && !resultClassesBean.getListResultClasses().isEmpty()) {
    for(String clazz : resultClassesBean.getListResultClasses()) {
        cl.loadClass(clazz);
    }
}
Ildelian
  • 1,278
  • 5
  • 15
  • 38