How to avoid resource conflicts in library jars?
I'm worried about the situation where libraries Foo
and Bar
each expose a resource in the classpath with the same name, like properties.txt
in this example.
Assuming Maven is configured and jars
deployed with Maven, if I have this:
Library Foo:
$ cat Foo/src/main/resources/properties.txt $ Foo
and the library panel:
$ cat Bar/src/main/resources/properties.txt $ Bar
And App
, which depends on them, whose pom
looks something like this: in a nutshell it just says "build jar-with-dependencies and depends 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>
The problem is that the file has properties.txt
been tailored. 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 class main
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 result:
unrollme-dev-dan:target Dan$ java -jar App-1.0-SNAPSHOT-jar-with-dependencies.jar
Scanner: Bar
Oops, Bar won. Warnings and errors in mvn package
-ing App
. No warnings or errors on startup that the wrong file may have been selected, in fact it was failing.
So I want to ask the correct practice to avoid this. First, something like this should fail loudly, not softly. Second, the only solution I can think of is that all resource files should be packaged correctly, like everything else in Java development, that is, the library should never be exposed properties.txt
in the "global" namespace; it should appear in the type folder me/unroll/deptest/foo
just like everything else. I'm skeptical that I haven't seen an example of Maven that actually does this. So what's the best thing here?
source to share
And what do you do in Java to avoid collisions between libraries? Packages! This is an established and well understood approach. Packages also work with resources:
com/example/foo/Foo.class
com/example/foo/properties.txt
and the second library:
com/example/bar/Bar.class
com/example/bar/properties.txt
Note what properties.txt
lies in different packages and therefore in the final JAR directories. In fact, this approach is preferred as the API for getting such resources becomes simpler:
App.class.getResourceAsStream("properties.txt"))
against.
Bar.class.getResourceAsStream("properties.txt"))
It just works because it Class.getResourceAsStream()
is local to the base class package by default. Of course, when you are inside an instance method Foo
, either or Bar
, you just say getClass().getResourceAsStream("properties.txt")
. Also, you can easily link to both files, just like you link to 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
. Default Spring expects the context of the file will be located under: /src/test/resources/com/example/foo/FooTest-context.xml
.
source to share