I am working on a customization for an ERP system in Java. In my customization I want to use Apache POI 3.10.1. Therefore I have integrated the jars poi-3.10.1-20140818.jar and poi-ooxml-3.10.1-20140818.jar.
However, these jars contains several classes that are already included in the core code of the ERP System, but have differences.
If the core ERP classes override the POI classes, the customization throws a Runtime exception. Possibly the same will happens with a core functionality if the POI classes override the core classes.
What is a best practice for dealing with a problem like this?
My customization is a relatively isolated functionality.
There are two approaches to solving this problem:
You can isolate the library from the ClassLoader
that loads the other version of POI. for now, I assume that the ERP system is on the class path such that you need to isolate the library from the system class loader. You can do so by creating a new instance of an URLClassLoader
which you then point to the jar files containing the newer version of POI. Make sure to also add all transient dependencies such as for example commons-codec to avoid class loading issues. Also, note that transient dependencies can have transient dependencies by themselves.
In order to hide the class path from a class loader, you would set the bootstrap class loader as a direct parent which is represented by null
:
new URLClassLoader(new URL[]{ new URL("poi-3.10.1-20140818.jar"), ... }, null);
With this class loader, you can query for the newer version POI classes by something like
Class.forName("org.apache.poi.hssf.usermodel.HSSFWorkbook", true, urlClassLoader);
for retreiving the new version of the HSSFWorkbook
. Note however that any direct reference to HSSFWorkbook
by a literal would be resolved by the class loader of the executing class which would of course link the old, incompatible version of a class. Thus, you need to use reflection for all your code. Alternatively, you add a class to the URLCLassLoader
which contains all your logic and only invoke this class via reflection. This is a cleaner approach, in general. For example, you could add a class that implements a bootstrap class such as Callable
which you then can use from any different context as for example:
Callable<File> sub = (Callable<File>) Class.forName("pkg.Subroutine",
true,
urlClassLoader);
File convertedFile = sub.call();
Alternatively, you can repackage the second POI dependency into another name space. After doing this, the classes are not conflicting anymore as their names are not longer equal. This is probably a cleaner approach as you can then use both libraries from the same class loader and you avoid reflection.
For repackaging a dependency into another name space, there are tools like the Maven Shade plugin that can help you with this task. Alternatives are jarjar for ant or the Shadow plugin for Gradle.
If you are using Servlet 3.0 API and you can change some configuration, "web fragments" can be utilized for such kind of situation. Following is the explanation: http://www.oracle.com/technetwork/articles/javaee/javaee6overview-part2-136353.html#webfrags