16

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?

Dave
  • 44,275
  • 12
  • 65
  • 105

3 Answers3

5

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
Community
  • 1
  • 1
creemama
  • 6,559
  • 3
  • 21
  • 26
2

Have you tried getResourceAsStream as suggested here:

how-to-a-read-file-from-jar-in-java

Community
  • 1
  • 1
John B
  • 32,493
  • 6
  • 77
  • 98
  • Sorry, I'm actually using `getResourceAsStream` already (I wrote `getResource` because I wasn't aware of any difference and it seemed simpler) – Dave Nov 22 '13 at 17:16
0

You can now use the maven-resources-plugin to share resources between your jars!

In the pom.xml file of the source module/jar, add the following plugin:

<artifactId>DATA_MODULE_NAME</artifactId>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-resources-plugin</artifactId>
        </plugin>
    </plugins>
</build>

This will tell maven to copy the contents of the resource folder along with the jar.

In the module you want to access those resources, add this to the dependencies in the pom:

<dependencies>
    <dependency>
        <groupId>${project.groupId}</groupId>
        <artifactId>DATA_MODULE_NAME</artifactId>
        <version>${project.version}</version>
    </dependency>
</dependencies>

When you build your final jar, the resources from the source module will be copied in and available using getResource()

Justin Gilman
  • 479
  • 4
  • 12
  • 2
    I think the original requirement here (a long time ago though!) was to be able to load data from a _separate_ jar file; not part of the compilation (e.g. the program in one jar, and the data jar moved into the correct location post-compilation but pre-launch). The idea being that the data could be updated frequently without a recompilation required. But it's also good to know that the related use-case of bundling resources at compile time has a possible solution. – Dave Oct 07 '21 at 22:32