Hi, I use Guava ClassPath class to scan and load some class in my project. It works fine when I run application in my IDE, but when I package it in a executable jar, it cannot load class. Spring boot package use a separate catelog 'lib' to setup dependency jars, is it not in classpath? How can I load these classes?

my code below:

   private static final ImmutableSet<Class<?>> REQUEST_CLASSES;

static {
    try {
        REQUEST_CLASSES = from(
                from(currentThread().getContextClassLoader()).getTopLevelClassesRecursive(
                        OSS_REQUEST_PACKAGE)).transform(
                new Function<ClassPath.ClassInfo, Class<?>>() {
                    @Override
                    public Class<?> apply(ClassPath.ClassInfo input) {
                        return input.load();
                    }
                }).toSet();
    }
    catch (IOException e) {
        LOGGER.error("Load OSS request classes exception", e);
        throw new RuntimeException(e);
    }

}

Comment From: cbelleza

Hi @GrapeBaBa

I have had this same problem as you described, to solve it I had to package the executable JAR as ZIP type.

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <layout>ZIP</layout>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

Cheers,

Comment From: GrapeBaBa

thanks, but it still not work. only change is in manifest, main class change to propertieslauncher

Comment From: GrapeBaBa

Anybody can give some advice?

Comment From: cbelleza

Is your project a web module? If yes, you may need to change the code to load resources using Classpath.getResourceAsStream().

Just to tell you on IDE I have got many successful cases, but when I move to create executable jars I am suffering about many issues like yours. It seems the class loader from jar is working different.

Comment From: GrapeBaBa

no, it is not a web project.

Comment From: philwebb

I imagine that Guava isn't supporting our nested jar format. Are you able to use Spring's ClassPathScanner instead?

Comment From: GrapeBaBa

Hi phil, I do not see a class named ClassPathScanner, where is it?

Comment From: philwebb

Sorry, my mistake. I meant PathMatchingResourcePatternResolver

Comment From: GrapeBaBa

PathMatchingResourcePatternResolver resolver=new PathMatchingResourcePatternResolver();
File file=resolver.getResource("lib/a.jar").getFile();
JarFile jarFile=new JarFile(file);

You mean like this? Y

Comment From: philwebb

Take a look at SpringPackageScanClassResolver for an example. This was how we got Liquibase to play nicely with fat jars.

Specifically:

private 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;
    }

Which finds all classes in a given package.

Comment From: philwebb

You might also want to look at ClassPathScanningCandidateComponentProvider.

Comment From: GrapeBaBa

Thanks, Phil. It works fine, why not export some public interface for this function?

Comment From: jhoeller

If you don't find this convenient enough, we could provide a package-name-based method variant in Spring's core PathMatchingResourcePatternResolver itself. However, if you condense the above code a bit, it's really just three lines of code that could even be expressed as one long line...

Comment From: GrapeBaBa

Actually, I need the findAllClasses method in SpringPackageScanClassResolver, it is not just three lines. I feel that you could provide a method with package name arg and return a set of class.

Comment From: philwebb

I'll close this one since there's not much we can do about Guava. If you're interested in getting a a convenience method in Spring please raise a JIRA.

Comment From: benc

FWIW, I've also encountered this issue, but had success with the Reflections library.

Comment From: yaohwu

same happened to me. All dependencies in one spring boot jar should make thing easier, but actually it makes thing more difficult. It even make java.lang.ClassLoader.loadClass cannot load dependency class.

Comment From: yaohwu

I tried many methods and finally succeeded. This is my experience.

https://github.com/yaohwu/notes/issues/16

Comment From: wilkinsona

@yaohwu As long as you're using the ClassLoader that's created by Spring boot, loadClass should be able to load every class and resource that's packaged in BOOT-INF/classes and within the jars in BOOT-INF/lib. The problem discussed this issue is (at least somewhat) specific to Guava due to faulty assumptions it makes about the structure of the classpath.

Comment From: yaohwu

@wilkinsona thanks for your reply. What I used classloder is generated by ClassLoader.getSystemClassLoader(),throw classnotfoundexception while the class actually in BOOT-INF/lib.

It looks just like this issue description. BOOT-INF/lib will not append into classpath like java -cp works. Is that?

Comment From: wilkinsona

Correct. The system class loader cannot see classes in BOOT-INF/classes or from within the jars in BOOT-INF/lib. To be able to load those classes, you should use the ClassLoader created by Spring Boot instead. It will be the thread context class loader when you launch your application. It will also be the class loader that loads your application's main class.

Comment From: yaohwu

Correct. The system class loader cannot see classes in BOOT-INF/classes or from within the jars in BOOT-INF/lib. To be able to load those classes, you should use the ClassLoader created by Spring Boot instead. It will be the thread context class loader when you launch your application. It will also be the class loader that loads your application's main class.

Thanks. I will try later.