I'm worried about the situation when libraries Foo
and Bar
each expose a resource on the classpath with the same name, say properties.txt
in this example.
Assuming a Maven set up and the jars
are deployed with Maven, if I have this set up:
Library Foo:
$ cat Foo/src/main/resources/properties.txt
$ Foo
and Library Bar:
$ cat Bar/src/main/resources/properties.txt
$ Bar
And an App
that depends on them, whose pom
looks something like this - in a nutshell this just says "build a jar-with-dependencies, and depend on Foo
and Bar
:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>bundle-project-sources</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<archive>
<manifest>
<mainClass>me.unroll.deptest.App</mainClass>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
<manifestEntries>
<Implementation-Build>${buildNumber}</Implementation-Build>
</manifestEntries>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</execution>
</executions>
</plugin>
Problem is it really seems like the properties.txt
file was clobbered. Let's try a jar tf
:
unrollme-dev-dan:target Dan$ jar tf App-1.0-SNAPSHOT-jar-with-dependencies.jar
META-INF/
META-INF/MANIFEST.MF
properties.txt
META-INF/maven/
META-INF/maven/me.unroll.deptest/
META-INF/maven/me.unroll.deptest/Bar/
META-INF/maven/me.unroll.deptest/Bar/pom.xml
META-INF/maven/me.unroll.deptest/Bar/pom.properties
META-INF/maven/me.unroll.deptest/Foo/
META-INF/maven/me.unroll.deptest/Foo/pom.xml
META-INF/maven/me.unroll.deptest/Foo/pom.properties
me/
me/unroll/
me/unroll/deptest/
me/unroll/deptest/App.class
So I ran a main
class in App
that does:
try (InputStream is = App.class.getClassLoader().getResourceAsStream("properties.txt")) {
java.util.Scanner s = new java.util.Scanner(is);
System.out.println("Scanner: " + s.next());
}
And the output is:
unrollme-dev-dan:target Dan$ java -jar App-1.0-SNAPSHOT-jar-with-dependencies.jar
Scanner: Bar
Whoops, Bar won. No warnings or errors when mvn package
-ing App
. No warnings or errors when running that the wrong file may have been chosen, in fact it failed silently.
So I want to ask proper practice to avoid this. One, something like this should fail loudly, not softly. Two, the only solution I can think is that all resource files should be properly packaged like everything else in Java development, i.e., a library should never expose properties.txt
in the "global" namespace; it should appear in a folder like me/unroll/deptest/foo
just like everything else. I'm skeptical because I haven't seen any Maven example that actually does this. So what is best practice here?
And what do you do in Java to avoid collisions between libraries? Packages! This is established and well understood approach. Packages work with resources as well:
com/example/foo/Foo.class
com/example/foo/properties.txt
and second library:
com/example/bar/Bar.class
com/example/bar/properties.txt
Note that properties.txt
lies in different packages and thus directories in final JAR. Actually this approach is preferred because the API for retrieving such resources becomes easier:
App.class.getResourceAsStream("properties.txt"))
vs.
Bar.class.getResourceAsStream("properties.txt"))
It just works because Class.getResourceAsStream()
is by default local to package of underlying class. Of course when you are inside an instance method of either Foo
or Bar
you simply say getClass().getResourceAsStream("properties.txt")
. Moreover you can still reference both files easily, just like you reference classes:
getClass().getResourceAsStream("/com/example/foo/properties.txt");
getClass().getResourceAsStream("/com/example/bar/properties.txt");
I'm skeptical because I haven't seen any Maven example that actually does this.
Real world example: you have a Spring integration test named com.example.foo.FooTest
. By default Spring expects the context file to reside under: /src/test/resources/com/example/foo/FooTest-context.xml
.
To complete the @Tomasz's answer with the maven code to do that :
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<targetPath>com/example/bar/</targetPath>
</resource>
</resources>
</build>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With