Meer met Maven – Juli 2013

In elke editie 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 class Laffer
{
 public void check( String breathSpray )
{
  if ( breathSpray.isEmpty() )
  {
    System.out.println( "Go to Quiki Mart" );
  }
 }
}

Bestaat toeval? Ik had me voor deze editie voorgenomen om iets te vertellen over Java Compatibility en prompt wordt mijn collega geconfronteerd met het probleem dat ik wil beschrijven.

Mijn collega heeft bovenstaand stukje code geschreven. De code compileert, alle unittesten en integratietesten slagen, dus wil hij het deployen op  één van de WAS6 servers. Daar krijgt hij al snel een foutmelding: java.lang.NoSuchMethodException:  String.isEmpty(). Wat is er fout gegaan?

In de pom wordt gebruik gemaakt van de meest recente maven-compiler-plugin, oftewel met de waarde 1.5 voor zowel <source> als <target>. Daarnaast heeft mijn collega JDK6 op zijn werkstation geïnstalleerd, terwijl WAS6 nog met JDK5 werkt. Nu blijkt dat de String.isEmpty()-methode pas beschikbaar is sinds JDK6. De source/target parameters zijn dus erg misleidend: ze controleren weliswaar de syntax van de desbetreffende  JDK-versie, maar niet de API signatures!

De meest eenvoudige manier om dit te voorkomen, is door ervoor te zorgen dat er altijd gecompileerd wordt tegen de juiste JDK-versie. Daar zijn een aantal mogelijkheden voor:

  • Laat de  JAVA_HOME omgevingsvariabele verwijzen naar de vereiste JDK installatie. Deze variabele gebruikt Maven om te draaien, maar wordt ook gebruikt als default voor de maven-compiler-plugin.
  • Maak gebruik van de <executable>-parameter van de maven-compiler-plugin. Hiermee kun je compileren tegen een andere JDK-versie dan waaronder Maven draait.
  • Maak gebruik van Maven toolchains. In het kort komt het erop neer, dat je in je pom.xml de maven-toolchains-plugin opneemt, waarin je een tool met bijbehorende versie configureert.  In een apart bestand in je home-directory kun je voor verschillende tools en versies refereren naar een installatie directory op jouw systeem.

Het nadeel van elke bovenstaande oplossing is dat het extra handelingen vereist van de ontwikkelaar (installatie van de juiste JDK en het goed zetten van verwijzingen), terwijl Maven juist een soort ‘plug-and-play’-principe nastreeft.

Vandaar dat mijn voorkeur uitgaat naar een project genaamd ‘Animal Sniffer’ van Codehaus Mojo. Dit project biedt de mogelijkheid om te controleren of de sources van jouw Java-project compatible zijn met een specifieke Java versie. ‘Animal’ verwijst naar de codenamen die Sun gebruikt voor de verschillende Java versies, welke vaak verwijzen naar dierennamen. Denk bijvoorbeeld  aan ‘Tiger’, ‘Mustang’ of ‘Dolphin’ .

Voor Maven projecten biedt Animal Sniffer twee oplossingen: controle via de animal-sniffer-maven-plugin of via een enforcer-rule, die je kunt opnemen in de maven-enforcer-plugin. In beide gevallen komt het erop neer, dat je een keuze maakt uit 1 van ruim 20 beschikbare API-signatures. De signatures zijn onderverdeeld in JDK-versie en ‘vendor’, want ook daartussen kunnen nog verschillen zijn.


 <plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>animal-sniffer-maven-plugin</artifactId>
  <version>1.9</version>
  <configuration>
   <signature>
    <groupId>org.codehaus.mojo.signature</groupId>
    <artifactId>java15</artifactId>
    <version>1.0</version>
   </signature>
  </configuration>
  </plugin>

Met de Animal Sniffer heb ik ervoor gezorgd dat mijn code geen gebruik meer kan maken van nieuwere API signatures. Maar hoe zit dat met de dependencies? Ook daarlangs kunnen nog te nieuwe classes binnen glippen. Wederom biedt Codehaus Mojo een:  de enforce-bytecode-version, één van de extra-enforcer-rules.  Voor wie de melding ‘Bad version number in .class file’ herkent: de enforce-bytecode-version maakt gebruik van dezelfde techniek voor het bepalen van de Java compatibility.


<plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-enforcer-plugin</artifactId>
 <version>1.0</version>
  <configuration>
   <rules>
    <enforceBytecodeVersion>
     <maxJdkVersion>1.5</maxJdkVersion>
    </enforceBytecodeVersion>
   </rules>
  </configuration>
 <dependencies>
  <dependency>
   <groupId>org.codehaus.mojo</groupId>
   <artifactId>extra-enforcer-rules</artifactId>
   <version>1.0-alpha-4</version>
  </dependency>
 </dependencies>
</plugin>

Door gebruik te maken van zowel Animal Sniffer als de Enforce Bytecode Version Enforcer-rule kun je in ieder geval garanderen dat elke build compatible is met de vastgestelde JDK-versie.