Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can not load class in classpath when package a executable jar #4375

Closed
GrapeBaBa opened this issue Nov 3, 2015 · 22 comments
Closed

Can not load class in classpath when package a executable jar #4375

GrapeBaBa opened this issue Nov 3, 2015 · 22 comments

Comments

@GrapeBaBa
Copy link

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

}
@cbelleza
Copy link

cbelleza commented Nov 4, 2015

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,

@GrapeBaBa
Copy link
Author

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

@GrapeBaBa
Copy link
Author

Anybody can give some advice?

@cbelleza
Copy link

cbelleza commented Nov 5, 2015

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.

@GrapeBaBa
Copy link
Author

no, it is not a web project.

@philwebb
Copy link
Member

philwebb commented Nov 5, 2015

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

@GrapeBaBa
Copy link
Author

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

@philwebb
Copy link
Member

philwebb commented Nov 6, 2015

Sorry, my mistake. I meant PathMatchingResourcePatternResolver

@GrapeBaBa
Copy link
Author

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

You mean like this?
Y

@philwebb
Copy link
Member

philwebb commented Nov 6, 2015

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.

@philwebb
Copy link
Member

philwebb commented Nov 6, 2015

You might also want to look at ClassPathScanningCandidateComponentProvider.

@GrapeBaBa
Copy link
Author

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

@jhoeller
Copy link

jhoeller commented Nov 9, 2015

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...

@GrapeBaBa
Copy link
Author

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.

@philwebb
Copy link
Member

philwebb commented Nov 9, 2015

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.

@philwebb philwebb closed this as completed Nov 9, 2015
@benc
Copy link

benc commented Feb 6, 2016

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

@yaohwu
Copy link

yaohwu commented Dec 6, 2021

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.

@yaohwu
Copy link

yaohwu commented Dec 7, 2021

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

yaohwu/notes#16

@wilkinsona
Copy link
Member

@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.

@yaohwu
Copy link

yaohwu commented Dec 7, 2021

@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?

@wilkinsona
Copy link
Member

wilkinsona commented Dec 7, 2021

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.

@yaohwu
Copy link

yaohwu commented Dec 7, 2021

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants