maven ist ein mächtiges Build-Management-Tool. Es ist sehr komplex, enthält Unmengen von Erweiterungen und ist leider sehr dürftig dokumentiert.
Copy-Paste - man fügt eine neue Abhängigkeit hinzu, die IDE bindet die Bibliotheken an und nach einigen Sekunden kann man loslegen. Mit der Zeit wächst die pom.xml und öfters
vergisst man die nicht mehr benötigten Abhängigkeiten zu entfernen. Als Resultat wird die kompilierte Anwendung unnötig größer .
Ferner dauert das Kompilieren deutlich länger, wenn die Abhängigkeiten erst mal heruntergeladen werden müssen.
Wie auch immer. Unnötige Abhängigkeiten sollen entfernt werden. Und so geht’s.
Die unbenutzten Abhängigkeiten sollen zuerst mit Hilfe des Befehls mvn dependency:analyze angezeigt werden. Dieser Befehl aktiviert den maven Plugin maven-dependency-analyzer, der nach der compile-Phase den Bytecode analysiert. Dies ist eine wichtige Eigenschaft des Analyzers. Es wird also nicht den Quelltext, sondern der übersetzte und vom Compiler optimierte Bytecode analysiert.
Führt man den Befehl in dem Projekt-Verzeichnis aus, sieht die Ausgabe etwa so aus:
[INFO] --- maven-dependency-plugin:2.10:analyze (default-cli) @ de.jawb.examples ---
[WARNING] Used undeclared dependencies found:
[WARNING] org.springframework:spring-beans:jar:4.3.4.RELEASE:compile
[WARNING] org.springframework.security:spring-security-core:jar:4.0.3.RELEASE:compile
[WARNING] org.springframework:spring-web:jar:4.3.4.RELEASE:compile
[WARNING] org.hibernate:hibernate-core:jar:5.0.5.Final:compile
[WARNING] Unused declared dependencies found:
[WARNING] mysql:mysql-connector-java:jar:5.1.10:compile
[WARNING] javax.transaction:jta:jar:1.1:compile
[WARNING] org.slf4j:slf4j-log4j12:jar:1.7.21:compile
[WARNING] javax.servlet:jstl:jar:1.2:compile
[WARNING] ...
Es gibt also Abhängigkeiten im Projekt, die
sind.
Eine Abhängigkeit, die verwendet wird, obwohl sie nicht in der pom.xml deklariert ist, ist eine transitive Abhängigkeit.
Das bedeutet, sie wurde mit einer anderen Abhängigkeit (sagen wir mal Abhängigkeit A) in das Projekt-Classpath “geschmuggelt”.
Verwendet man eine solche Bibliothek, kann dies unter Umständen sehr unangenehm werden, wenn eine neuere Version der Abhängigkeit A
diese Bibliothek nicht mehr verwendet oder, wenn die Schnittstelle der Bibliothek geändert wurde (apache-commons?). Das Projekt kompiliert nicht und die Fehlersuche
kann viel Zeit und Nerven kosten.
Solche Abhängigkeiten sollten also immer explizit in der pom.xml deklariert werden. Hat man das getan,
wird Maven-Analyzer beim erneuten Ausführen keine Warnung diesbezüglich mehr geben.
Hier ist Vorsicht geboten. Falls Maven-Analyzer solche Abhängigkeiten (wie in unserem Fall) gefunden hat, sollte man ganz genau hinschauen, ob man diese auch wirklich entfernen darf. Wie bereits erwähnt, testet der Analyzer nur den Bytecode. Die Klassen die zur Laufzeit geladen werden, können von Analyzer nicht erkannt werden. Das wohl bekannteste Beispiel ist das Laden einer Treiberklasse:
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://host:port/db","username", "password");
Genau aus diesem Grund wird die Abhängigkeit mysql:mysql-connector-java:jar:5.1.10:compile als “unused declared” erkannt. Diese darf also nicht entfernt werden.
Da die Konstanten in Java beim Übersetzen "inlined" werden, kann der Analyzer auch in solchen Fällen die Abhängigkeit zu einer Bibliothek, in der solche Konstanten definiert sind, nicht sehen. Das gleiche gilt für Annotationen mit @Retention(RetentionPolicy.SOURCE) sowie Links in Javadoc.
Die Abhängigkeitsanalyse kann immer nach der Compile-Phase via mvn clean install automatisch durchgeführt werden. Der Build-Vorgang wird dabei abgebrochen, falls Maven-Analyzer mindestens eine Warnung gibt. Dazu muss nur das Plugin in der pom.xml entsprechend konfiguriert werden.
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.10</version>
<executions>
<execution>
<id>analyze</id>
<goals>
<goal>analyze-only</goal>
</goals>
<configuration>
<failOnWarning>true</failOnWarning>
<outputXML>true</outputXML>
</configuration>
</execution>
</executions>
</plugin>
Das sieht schon mal vielversprechend aus. Was machen wir aber mit den Abhängigkeiten die fälschlicherweise als “unused” erkannt werden? Nun dazu gibt es Möglichkeit solche Abhängigkeiten zu ignorieren:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.10</version>
<executions>
<execution>
<id>analyze</id>
<goals>
<goal>analyze-only</goal>
</goals>
<configuration>
<failOnWarning>true</failOnWarning>
<ignoredDependencies>
<ignoredDependency>mysql:mysql-connector-java</ignoredDependency>
</ignoredDependencies>
</configuration>
</execution>
</executions>
</plugin>
Mit Hilfe dieses Ruby-Scripts können die Maven-Abhängigkeiten ganz einfach entfernt werden. Das Script verwendet nicht mvn dependency:analyze, stattdessen entfernt es eine Abhängigkeit aus der pom.xml und führt mvn clean install durch. Falls es zu einem Fehler kommt, wird die Abhängigkeit in die pom.xml zurückgeschrieben. Dann wird die nächste entfernt und s.w.
Dies ist eine gute (bruteforce-) Methode, da es sichergestellt wird, dass die Anwendung kompiliert und alle Tests durchlaufen. Bei einer guten Testabdeckung sollte diese Methode auf jeden Fall berücksichtigt werden. Allerdings erfordert sie viel Geduld sowie eine Ruby-Installation...