Search code examples
javaclasspathjsvc

Access resources from another jar file


I have a simple structure: A data jar file which contains a batch of data, and a service jar file, which runs a service using the data. To make the data easy to replace, I have them separate, and service.jar's classpath contains the directory which data.jar is in.

Within service.jar, I use getResource to load the data files. This works if the data files are directly within the folder, but fails when they are inside data.jar;

This fails:

all
+ globalclasspath
| + data.jar
|   + mine.properties
+ daemons
  + service.jar

jsvc -cp globalclasspath:daemons/service.jar (...)

MyClass.class.getClassLoader( ).getResource( "mine.properties" ); // <-- null

But this works:

all
+ globalclasspath
| + mine.properties
+ daemons
  + service.jar

jsvc -cp globalclasspath:daemons/service.jar (...)

MyClass.class.getClassLoader( ).getResource( "mine.properties" ); // <-- not null

I don't want to change the classpath (unless I can change it to something generic which doesn't depend on the name of the data jar file), but I'm fine with changing the getResource string (I've tried /data/mine.properties and /data.jar/mine.properties to no avail). Is there a change I can make so that the resources can be loaded from within the jar?


Solution

  • Solution 1

    Use a classpath wildcard.

    jsvc -cp globalclasspath/*:daemons/service.jar (...)
    

    See "How to use a wildcard in the classpath to add multiple jars?"

    Solution 2

    To read data in JARs not on the classpath, use URLClassLoader. The general algorithm is this:

    1. Find the list of JARs in the globalclasspath directory.
    2. Create a URLClassLoader from this list of JARs.
    3. Look up the resource you want from the URLClassLoader instance.

    To find JARs on the classpath, I used ResourceList from the StackOverflow article "Get a list of resources from classpath directory."

    public class MyClass {
        /**
         * Creates a {@code URLClassLoader} from JAR files found in the
         * globalclasspath directory, assuming that globalclasspath is in
         * {@code System.getProperty("java.class.path")}.
         */
        private static URLClassLoader createURLClassLoader() {
            Collection<String> resources = ResourceList.getResources(Pattern.compile(".*\\.jar"));
            Collection<URL> urls = new ArrayList<URL>();
            for (String resource : resources) {
                File file = new File(resource);
                // Ensure that the JAR exists
                // and is in the globalclasspath directory.
                if (file.isFile() && "globalclasspath".equals(file.getParentFile().getName())) {
                    try {
                        urls.add(file.toURI().toURL());
                    } catch (MalformedURLException e) {
                        // This should never happen.
                        e.printStackTrace();
                    }
                }
            }
            return new URLClassLoader(urls.toArray(new URL[urls.size()]));
        }
    
        public static void main(String[] args) {
            URLClassLoader classLoader = createURLClassLoader();
            System.out.println(classLoader.getResource("mine.properties"));
        }
    }
    

    I ran the following command:

    java -cp globalclasspath:daemons/service.jar MyClass
    

    The terminal output:

    jar:file:/workspace/all/globalclasspath/data.jar!/mine.properties