jQAssistant – Entdecke Deine Java-Anwendung

Logo-jQA-kurz

Anwendungen wachsen schnell – oftmals so schnell, dass es ab einer gewissen Größe nicht ohne unerhebliche Aufwände möglich ist, Informationen über entstandene Strukturen zu erhalten. Wie kann man beispielsweise schnell eine Übersicht über an JPA Entitäten definierte Named Queries erhalten?

jQAssistant ist ein Maven-Plugin, welches die erzeugten Klassen – also den Bytecode – einer Java-Anwendung analysiert und die gewonnenen Informationen in einer Graphendatenbank (Neo4j) ablegt. Es erzeugt darin Knoten, welche Packages, Klassen, Felder, Methoden, etc. repräsentieren. Diese verfügen je nach Typ über Properties (z.B. Namen, Access Modifier) und Beziehungen zu anderen Knoten. Letztere bilden beispielsweise Vererbungshierarchien, Feld- und Methodenzugriffe ab. Mittels Cypher, der Abfragesprache von Neo4j, lassen sich über diese Strukturen sehr flexible und performante Abfragen formulieren, die über die Möglichkeiten von Suchfunktionen in IDEs weit hinausgehen. Das soll im Folgenden veranschaulicht werden.

Auf GitHub steht eine Petstore-Anwendung zur Verfügung, welche diverse Konzepte von Java EE 6 zu Demonstrationszwecken umsetzt. Diese kann mittels

git clone
  https://github.com/agoncal/agoncal-application-petstore-ee6.git

ausgecheckt und mit dem folgenden Kommando gebaut werden:

mvn install

Das Scannen der Java-Klassen durch jQAssistant wird mit dem Kommando

mvn com.buschmais.jqassistant.maven:jqassistant-maven-plugin:scan

ausgelöst (und dauert nur wenige Sekunden). Anschließend kann mit

mvn com.buschmais.jqassistant.maven:jqassistant-maven-plugin:server

der eingebettete Neo4j-Server gestartet werden, dessen Weboberfläche anschließend unter der URL „http://localhost:7474/webadmin“ erreichbar ist. Die Menüpunkte „Data browser“ bzw. „Console“ erlauben die Ausführung von Cypher-Abfragen. Die Anzahl gefundener Java-Typen (Klassen, Interfaces, Enumerationen oder Annotationen) kann jetzt z.B. folgendermaßen ermittelt werden:

match
  (t:TYPE)
return
  count(t)

Die Abfrage sucht („match“) in der Graphendatenbank alle Knoten „t“, welche mit dem Label „TYPE“ versehen sind, und gibt als Ergebnis deren Anzahl zurück. Eine kleine Änderung liefert für jeden einzelnen Typen die Anzahl der darin deklarierten Methoden:

match
  (t:TYPE)-[:CONTAINS]->(m:METHOD)
return
  t.FQN as Type, count(m) as Methods
order by
  Methods desc

Die Match-Klausel ist der interessanteste Teil. Sie beschreibt die Suche nach einer Beziehung zwischen Knoten und kann dabei gut als ASCII-Art interpretiert werden: Knoten werden durch „(…)“ und gerichtete Beziehungen durch „-[]->“ symbolisiert. Konkret handelt es sich bei dem Suchmuster also um „CONTAINS“-Beziehungen zwischen Knoten „t“ mit dem Label „TYPE“ zu Knoten „m“ mit dem Label „METHOD“. Zurückgegeben werden jeweils der vollqualifizierte Name von t („FQN“) sowie die Anzahl der dazugehörigen Methoden. Es ist ersichtlich, dass für die Formulierung der Abfragen das zugrundeliegende Datenmodell bekannt sein muss – dieses ist im jQAssistant-Wiki (https://github.com/buschmais/jqassistant/wiki) dokumentiert.
Damit kann die eigentliche Frage nach Named Queries wieder aufgegriffen werden. In einer Java-Klasse werden diese wie folgt deklariert:

@NamedQueries({
      @NamedQuery(name = Order.FIND_ALL, query = "SELECT o FROM Order o")
})
public class Order {

jQAssistant erzeugt für jede gefundene Annotation einen Knoten mit dem Label „ANNOTATION“. Dieser verfügt laut Datenmodell über folgende Beziehungen:

  • „OF_TYPE“: referenziert einen Knoten mit dem Label „TYPE“, welcher die Java-Klasse der Annotation repräsentiert, z.B. „javax.persistence.NamedQuery“
  • „HAS“: referenziert eine Menge von Knoten mit dem Label „VALUE“, welche die Attribute der Annotation darstellt. Jedes Attribut verfügt über eine Property „NAME“ sowie im vorliegenden Fall über eine Property „VALUE“.

Eine entsprechende Abfrage muss also alle Annotations-Knoten suchen, die den Typ „javax.persistence.NamedQuery“ referenzieren. Zurückgeben werden sollen jeweils die Attribute „NAME“ und „VALUE“ der ebenfalls durch den Annotations-Knoten referenzierten Value-Knoten. Der entsprechende Cypher-Ausdruck lautet:

match
  (annotation:ANNOTATION)-[:OF_TYPE]->(type:TYPE),
  (annotation)-[:HAS]->(nameAttribute:VALUE),
  (annotation)-[:HAS]->(queryAttribute:VALUE)
where
  type.FQN="javax.persistence.NamedQuery"
  and nameAttribute.NAME="name”
  and queryAttribute.NAME="query"
return
  nameAttribute.VALUE as Name, queryAttribute.VALUE as Query

jQAssistant - Named Queries

Screenshot: jQAssistant – Named Queries

Die Abfrage liefert aktuell 11 Ergebnisse – eine recht geringe Anzahl, welche durch eine manuelle Suche unter Zuhilfenahme der Werkzeuge moderner IDEs ggf. ebenso schnell hätte realisiert werden können. Doch wie sähe es mit einer Anwendung aus, die unter Beteiligung vieler Entwickler über mehrere Jahre gewachsen ist?

Kommentare sind abgeschaltet.