Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it correct or incorrect for a Java JAR to contain its own dependencies?

I guess this is a two-part question. I am trying to write my own Ant task (MyFirstTask) that can be used in other project's build.xml buildfiles. To do this, I need to compile and package my Ant task inside its own JAR. Because this Ant task that I have written is fairly complicated, it has about 20 dependencies (other JAR files), such as using XStream for OX-mapping, Guice for DI, etc.

I am currently writing the package task in the build.xml file inside the MyFirstTask project (the buildfile that will package myfirsttask.jar, which is the reusable Ant task).

I am suddenly realizing that I don't fully understand the intention of a Java JAR. Is it that a JAR should not contain dependencies, and leave it to the runtime configuration (the app container, the runtime environment, etc.) to supply it with the dependencies it needs? I would assume if this is the case, an executable JAR is an exception to the rule, yes?

Or, is it the intention for Java JARs to also include their dependencies?

Either way, I don't want to be forcing my users to be copying-n-pasting 25+ JARs into their Ant libs; that's just cruel. I like the way WAR files are set up, where the classpath for dependencies is defined under the classes/ directory.

I guess, ultimately, I'd like my JAR structure to look like:

myfirsttask.jar/
    com/  --> the root package of my compiled binaries
    config/  --> config files, XML, XSD, etc.
    classes/  --> all dependencies, guice-3.0.jar, xstream-1.4.3.jar, etc.
    META-INF/
        MANIFEST.MF

I assume that in order to accomplish this (and get the runtime classpath to also look into the classes/ directory), I'll need to modify the MANIFEST.MF somehow (I know there's a manifest attribute called ClassPath, I believe?). I'm just having a tough time putting everything together, and have a looming/lingering question about the very intent of JARs to begin with.

Can someone please confirm whether Oracle intends for JARs to contain their dependencies or not? And, either way, what I would have to do in the manifest (or anywhere else) to make sure that, at runtime, the classpath can find the dependencies stored under the classes/ directory? Thanks in advance!

like image 769
IAmYourFaja Avatar asked Dec 27 '22 17:12

IAmYourFaja


2 Answers

The term 'JAR file' can mean at least two things, or rather, has at least two facets to its meaning. Most basically, it means a container format: basically, a ZIP file with a META-INF directory. More refinedly, it means this container used as a way to package class files.

In the sense of being a container, there is no intent with respect to contents; the file could contain class files, other JARs (in either sense!), etc. But in the sense of being a packaging of code, i believe the intent for JAR files proper is for them not to contain any dependencies.

If you have a read of the JAR File Specification, you'll find there are several allusions to the storage of class files, but nothing about storing other JAR files. Correspondingly, if you look at the implementation of the JAR file classloader in the JRE, it can't do anything useful with nested JARs.

Furthermore, the JAR specification does detail a mechanism for dealing with non-nested dependencies: the Class-Path attribute. This lets a JAR file make relative references to other JAR files in the filesystem.

Now, in-the-sense-of-a-packaging JAR files are not the only use of in-the-sense-of-a-container JAR files. WAR, EAR, and RAR files (and more besides) are all JAR files used for particular purposes. Each of those is capable of containing other JARs: WARs can contain in-the-sense-of-a-packaging JAR files, and EARs can contain those and also WARs. However, those are quite different beasts than in-the-sense-of-a-packaging JAR files. It's worth noting that special classloaders, that are not in the Java standard library, are needed to make use of them.

The way that WARs etc can collect many JAR files together is indeed very useful, and it's a real shame there's no generic mechanism for doing this in Java outside of Java EE. It would be great to have an 'application archive' or 'meta-archive' format that simply bundled some JARs.

So, you're left with this problem of users needing 25 JARs in order to use your plugin. You have roughly two options.

First, you accept the pain, and distribute your plugin as a zip full of JARs, which users will have to unpack.

Secondly, you join the 21st century, and use a build tool and distribution mechanism which handles dependencies automatically: in practice, that means using Gradle, or Maven, or some other tool (such as Ant) in concert with Ivy, to obtain dependencies from Maven Central, and then then releasing your code along with a POM file which lists those dependencies. Users can then download your JAR and your POM, and have their own build tool obtain the dependencies.

If you do go the second route, it might be prudent to also release a zip of the dependencies, for the benefit of users who are not using automatic dependency management.

like image 108
Tom Anderson Avatar answered Mar 24 '23 05:03

Tom Anderson


The intent (AFAIU) is for JAR files to behave like native code shared object files (.so on Unix, .dll on Windows). Generally, an application will install several shared object files as siblings, plus an executable with which to launch them.

An executable JAR is more like a standalone executable, and so it is more common to include all dependencies (similar to the way a statically-linked native code executable contains all its dependent objects directly).

Unfortunately, the default ClassLoader is not able to load classes from nested JARs. It is possible to write a ClassLoader that does. Or you can use one someone else has written. From the description of your problem, it sounds like Jar Jar Links is exactly what you're looking for.

like image 45
Daniel Pryden Avatar answered Mar 24 '23 04:03

Daniel Pryden