Kotlin Symbol Processing API

Wie schreibe ich meinen eigenen Annotationsprozessor?

Wahrscheinlich hat jeder Android-Entwickler oder jede Android-Entwickler:in schon einmal damit zu tun gehabt: annotation processing. Als populäres Beispiel lässt sich hier etwa die Datenpersistenz Bibliothek Room nennen. Um hier die Annotationen wie beispielsweise @Database oder @Entity verwenden zu können, wurde ursprünglich das Kotlin Annotation Processing Tool (KAPT) verwendet. Allerdings befindet sich KAPT bereits im maintenance mode (Abbildung 1) und soll perspektivisch durch die Kotlin Symbol Processing API ersetzt werden.

Kotlin Using kapt

Mithilfe von KSP sollen leichtgewichtige Compiler-Plugins entwickelt werden können, indem die Leistungsfähigkeit von Kotlin genutzt wird. Zudem soll KSP im Vergleich zu KAPT die Annotationsprozessoren doppelt so schnell ausführen können.

Der eigene Annotationsprozessor

kotlin printIn-Statement

Unser hier erstellter Annotationsprozessor soll es ermöglichen, Funktionen mit der Annotation @LoggerFunction zu annotieren. Ist eine solche Funktion dementsprechend annotiert, soll eine neue Funktion mit einem übergebenen Namen erstellt werden, welche die Übergabeparameter der annotierten Funktion loggt. Der Einfachheit halber wird hier auf ein println-Statment gesetzt.

Projektaufbau

Für das folgende Projekt ergibt sich folgende Struktur hinsichtlich der Module:

An sich existieren drei Grundmodule. Die einzelnen Funktionalitäten und Verantwortlichkeiten werden im Folgenden näher diskutiert.

Kotlin Grundmodule

Annotations

Das annotations-Modul ist relativ überschaubar. Hier werden die entsprechenden Annotationen, die vom Annotationsprozessor verarbeitet werden sollen, festgelegt. In diesem Beispiel sieht die Annotation wie folgt aus:

Kotlin annotations-Modul

Das @Target definiert den entsprechenden Endpunkt (in diesem Fall sind es Funktionen), der annotiert werden kann. Zudem soll der hier zu erstellende Annotationsprozessor anhand des übergebenen Namens innerhalb der Annotation die Logfunktion erstellen. Daher wird hier entsprechend der Übergabeparameter name definiert.

Processor

Der logische Baustein innerhalb des Konzepts bildet das processor-Modul. Hier wird definiert, inwiefern die Annotationen verarbeitet und der durch die Annotationen automatisch generierte Code erstellt werden soll. Dafür hauptsächlich verantwortlich ist der FunctionProcessor:

Kotlin FunctionProcessor

Innerhalb der überschriebenen Funktion process findet dann die Verarbeitungslogik statt. Das Verarbeiten dessen kann wie folgt aussehen:

Kotlin process

Zudem wird ein sogenannter Visitor benötigt, der mit für die automatische Generierung der annotierten Funktionen zuständig ist. Dieser kann beispielsweise als innere Klasse definiert werden, wie folgendermaßen dargestellt:

Kotlin Visitor

Wichtig zu erwähnen ist hierbei die Funktion visitFunctionDeclaration. Diese entsprechende Implementierung sorgt dafür, dass das Grundgerüst der zu generierenden Funktionen aufgebaut wird. Zudem erkennt man in Zeile 45 den Funktionsaufruf visitValueParameter. Dies ist ebenfalls eine Funktion, die überschrieben werden kann. Die Verantwortlichkeit hierbei liegt in der Verarbeitung der Funktionsparameter der annotierten Funktion:

Kotlin VisitValueParameter

Zuletzt wurde noch eine Hilfsfunktion geschrieben, die für das Verarbeiten der Typen der Funktionsargumente genutzt wird:

Kotlin visitTypeArguments

Somit ist der Prozessor vollständig implementiert und kann auf Basis der annotierten Funktionen die zu erstellenden Funktionen bereitstellen. Um diesen Funktionsprozessor allerdings verwenden zu können, sind noch zwei weitere Schritte notwendig. Zum einen muss ein sogenannter SymbolProcessorProvider bereitgestellt werden, um den Prozessor zur Verfügung zu stellen. Dies passiert durch die folgende Implementierung:

Kotlin SymbolProcessorProvider

Des Weiteren muss eine Ressource erstellt werden, um den gerade aufgezeigten SymbolProcessorProvider zu registrieren:

Diese Datei befindet sich im resources-Ordner und muss der dargestellten Namenskonvention folgen. Der Inhalt dieser Datei beherbergt die Lokation des SymbolProcessorProviders. In diesem Falle wäre das: de.ams.FunctionProcessorProvider.

kotlin registration SymbolProcessorProvider

Dies wäre somit die komplette Implementierung des Annotationsprozessors innerhalb des processor-Moduls. Zu guter Letzt fehlt lediglich noch die Verwendung innerhalb des main-Moduls.

Main

Das main-Modul enthält ausschließlich eine Datei mit sowohl einer main-Funktion, die das Hauptprogramm darstellt, als auch einer test-Funktion, die mit unserer Annotation annotiert werden soll.

Kotlin LoggerFunction

Nach dem erfolgreichen Bauen der App kann die durch die Annotation bereitgestellte Funktion testLog verwendet werden, um die Übergabeparameter zu loggen. Die Ausgabe sieht dann wie folgt aus:

kotlin testLog

Vorteile der Kotlin Symbol Processing API

Die Vorteile der Kotlin Symbol Processing API zur Erstellung leichtgewichtiger Kotlin Compiler-Plugins sind folgende:

  • Ähnliche Funktionen wie KAPT, jedoch 2x schneller
  • Erhöhte Buildgeschwindigkeit
  • Direkter Zugriff auf Kotlin-Compilerfunktionen
  • Direktes Parsen von Kotlin Code
  • Stub-Generierung von KAPT nicht mehr notwendig

Erste Benchmarks zeigten bei der Persistenzbibliothek Room eine doppelte Geschwindigkeit gegenüber KAPT. Das Ziel bei KSP ist es, das Annotationsverarbeitungssystem KAPT vollständig durch KSP zu ersetzen.

Kotlin Symbol Processing API bereits unterstützte Bibliotheken

Derzeit (Stand 19. Januar 2022) werden folgende Bibliotheken unterstützt:

Kotlin unterstützte Bibliotheken

Die dargestellte Abbildung zeigt auf, dass lediglich eine Handvoll an Bibliotheken zurzeit unterstützt bzw. evaluiert werden. Trotzdessen kann man hier behaupten, dass sich diese Liste mit der Zeit füllen wird, um das definierte Ziel von KSP zu erreichen.

Kotlin Symbol Processing API – Fazit

Die Kotlin Symbol Processing API verspricht eine zukunftsorientierte Schnittstelle zur Verarbeitung von Annotationen durch den direkten Zugriff auf Kotlin-Compilerfunktionen zu werden. Vermutlich muss man hier allerdings noch etwas Geduld mitbringen, bis KSP vollständig das bisherige KAPT System ersetzt. Dennoch ist es sinnvoll, sich bereits jetzt mit dieser Thematik auseinanderzusetzen und die Kotlin Symbol Processing API im Auge zu behalten.

Mehr zum Thema Kotlin

×
Telefon

Sie sind auf der Suche nach einem Experten im Bereich App-Entwicklung? Wir freuen uns auf Ihre Nachricht!

+49 231 99953850
×