Meer met Maven – Least Fat Executable Jar

In elke editie van het Java Magazine zal Robert Scholte een probleem voorleggen en deze oplossen met behulp van Maven, om meer inzicht te geven in Maven zelf en de vele beschikbare plugins.


public static void main( String[] args )
{
  org.slf4j.LoggerFactory.getLogger( Eric.class )
    .warn( "I'm not fat! I'm getting in shape!" );
}

Stel dat ik een class met deze ogenschijnlijk simpele methode wil uitvoeren als executable jar, dan heb ik een interessante uitdaging: hoe krijg ik de dependencies naar SLF4J  op het classpath?

Voor het uitvoeren van dit soort standalone applicaties zijn er een klein aantal opties. De meest klassieke oplossing is het verzamelen van alle jars (wat kan via dependency:copy-dependencies)  en alles uitschrijven op de commandline:


.\target>java -cp awesom-0.4000-SNAPSHOT.jar; dependency\slf4j-api-1.5.6.jar;dependency\slf4j-simple-1.5.6.jar cgi.maven.nljug.Eric

Door een manifest-file op te nemen in de jar kan ik dit al een stuk vereenvoudigen, want daarin kan ik zowel het classpath  als de Main-class opgeven. Vandaar dat bijna alle ‘packaging’-plugins voorzien zijn van een optie <archive><manifest/></archive>, waarin je dit soort details in kwijt kunt. In dit geval gaat het om de opties <addClasspath/> en <mainClass/>.

Op dit moment bestaat deze deliverable uit meerdere bestanden, maar mijn wens is om dit terug te brengen tot één bestand, oftewel een ‘fat jar’. Het opnemen van jars in een jar is weliswaar in theorie mogelijk, maar daarmee komt de dependency-jar zelf op het classpath en niet de inhoud van deze jar. De enige optie die je hebt, is door ervoor te zorgen dat alle bestanden van de verschillende jars gebundeld worden in één jar.

De eerste plugin waarmee dit mogelijk werd was de Maven Assembly Plugin, maar tegenwoordig is er een plugin die hier veel beter op aansluit, namelijk de Maven Shade Plugin. Zonder enige aanpassing aan de configuratie maakt deze plugin van je project al direct een fat-jar.

Nadat ik met fat-jar heb gegenereerd heb ik weliswaar alle classes samengepakt in één jar, maar de kans is zeer groot dat slechts een klein deel van alle beschikbare bestanden gebruikt wordt. Het zou heel mooi zijn, als ik de jar kon ontdoen van onnodige ballast. Met de optie <minimizeJar> is het mogelijk alleen de gebruikte classes op te nemen in de jar .

Met de volgende configuratie transformeer ik mijn project tot de meest compacte executable jar:


<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-shade-plugin</artifactId>
  <version>2.0</version>
  <executions>
    <execution>
      <goals>
        <goal>shade</goal>
      </goals>
      <configuration>
        <transformers>
          <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
            <mainClass>cgi.maven.nljug.Eric</mainClass>
          </transformer>
        </transformers>
        <minimizeJar>true</minimizeJar>
      </configuration>
    </execution>
  </executions>
</plugin>

Voor de minimizeJar is een kleine kanttekening: dit werkt alleen zolang er naar de classes gerefereerd wordt. Het zal niet werken als er gebruik gemaakt wordt van reflection in de code, zoals bijvoorbeeld Class.forName("crazy.old.Driver").

Bekijk http://maven.apache.org/plugins/maven-shade-plugin/ voor verdere details en andere mogelijkheden met de Maven Shade Plugin.