Dependency Injection in Java - Alternativen zum reflektiven Zugriff

Wie im letzten Teil  des Blogs zum Thema Dependency Injection beschrieben, ist spätestens mit Einführung von Java 9 das Geschmäckle von DI in private Felder per Reflektion nicht wegzudiskutieren. Immerhin muss dafür die JVM mit einem Flag namens --permit-illegal-access initialisiert werden um den reflektiven Zugriff auf fremde Module zu erlauben. Auch namhafte IDEs markieren Instanzvariablen die privat sind und gleichzeitig mit @Inject annotiert sind mit Warnungen. Hinzu kommt, dass dieser Weg dem geneigten Android-Entwickler dieser dunkle Pfad der DI vollständig verwehrt bleibt. Dort werden aber bereits Alternativen wie Dagger angeboten, auf deren Realisierung ich weiter unten eingehen werde.

Wie kommen wir also zu einem sauberen Weg Abhängigkeiten zu injezieren ohne den gewohnten Komfort für den Entwickler einzubüßen? Schauen wir uns nochmals das ursprüngliche Beispiel an.

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Person
{
private String firstName;
private String familyName;
@Inject
private EventBus eventBus;
public void setFamilyName(String familyName) {
PropertyChangeEvent event = new PropertyChangeEvent(this,"familyName",
this.familyName,familyName);
this.familyName = familyName;
eventBus.publishEvent(event);
}
public String getFamilyName() {
return familyName;
}

}

Ganz ohne Anpassung des oben genannten Codes kann es meines Erachtens gar nicht funktionieren, will man nicht den Teufel mit dem Belzebub vertreiben in dem in die Art wie Klassen geladen werden eingreifen.

Variante 1: Setter + Annotation

Wenn wir also den bestehenden Code anpassen müssen, fällt einem vielleicht als erstes ein, die @Inject-Annotation statt an die Instanzvariable lieber an die Setter-Methode zu schreiben. Dazu muss diese Methode freilich erstmal erzeugt werden.


1
2
3
4
@Inject
public void setEventBus(EventBus eventBus){
this.eventBus = eventBus;
}

Was haben wir dadurch gewonnen?

Die Injektion erfolgt nun über diese Methode. Diese ist als public deklariert und darf damit guten Gewissens benutzt werden.

Was haben wir dadurch verloren?

Wir haben die Kapselung aufgegeben. Der EventBus kann nun jederzeit während des Lebenszyklus einer Personen-Instanz neu gesetzt werden. Das kann als positiver Aspekt gewertet werden, weil der Code an dieser Stelle flexibler geworden ist, allerdings ist im konkreten Fall vielleicht recht unwahrscheinlich, dass der Lebenszyklus eines EventBus’ kürzer ist als der Zyklus der Personen-Instanz, die den Bus lediglich verwendet um ihre PropertyChangeEvents zu propagieren.

Variante 2: Access-Modifier aufweichen

Wir ändern einfach die Deklaration der Instanzvariablen zu.

1
2
@Inject
EventBus eventBus

Durch das Weglassen des Access-Modifiers gilt der implizite oft als “package friendly” bezeichnete Zugriff. Das bedeutet, dass alle Klassen, die im gleichen Package wie unsere Person-Klasse liegt nun Zugriff auf die Instanzvariable haben.

Was haben wir gewonnen?

Genau wie in Variante 1 haben wir jetzt legalen Zugriff auf das Feld eventBus. Die Zugriffsbeschränkung auf Klassen des gleichen package ist zwar ein bisschen restriktiver und kommt somit der Kapselung zu Gute. Bevor das Modulsystem von Java 9 eingeführt wurde, war diese Restriktion allerdings leicht über sogenannte Spile-Packages umgehbar. Wollte man auf eine Package-friendly Variable aus dem javax.abc package aus dem rt.jar zugreifen, genügte es, selbst ein Package javax.abc in seinem workspace anzulegen und schon hatten alle Klassen darin Zugriff auf die package-friendly Variablen des entsprechenden packages im rt.jar.

Mit der Einführung des Modulsystems ist damit Schluß. Es gibt keine Split Packages mehr, so dass die intendierte Kapselung erhalten bleibt.

Dagger 2 ist ein DI Framework, dass diese package friendly deklarierten Felder beschreiben kann.. Mit Hilfe von Annotation Processing werden dabei zur Compile-Zeit alle mit @Inject annotierten Felder von Klassen analysiert. Anschließend werden automatisch Zugriffsklassen generiert, die im gleichen Package liegen wie unsere Person. Zur Laufzeit werden dann diese Zugriffsklassen verwendet um DI zu realisieren, dabei kann vollständig auf Reflektion verzichtet werden, weshalb dieser Weg auch funktioniert, wenn wir uns im Android-Umfeld bewegen.

Was haben wir verloren?

Im Grunde sind es die gleichen Nachteile wie auch schon bei Variante 1. Die Instanzvariable eventBus ist jetzt von außen jederzeit beschreibbar, obwohl im konkreten Fall unwahrscheinlich ist, dass ein anderer EventBus während des Lebenszyklus der Person-Bean zum Einsatz kommen wird.

Varianten 3: Initializer verwenden

Wir ersetzen die Injektion durch statische Initialisierung während der Objekterzeugung.

1
2
@Inject
private EventBus eventBus = Injector.inject (this);

Was haben wir gewonnen?

Jetzt bleibt der private Zugriff auf den eventBus wo er hingehört: Im Inneren der Klasse. Es ist sichergestellt, dass über den gesamten Lebenszyklus der Person-Instanz immer der gleiche EventBus benutzt wird. Es ist außerdem kein Zugriff per Reflektion notwendig.

Was haben wir verloren?

Kurz gesagt: die Dependency Injection. In dieser Variante wird gerade nicht eine Abhängigkeit im Nachgang nach der Erzeugung des Objekts injeziert. Auf der anderen Seiten machen wir ja auch keine DI um ihrer selbst willen, sondern weil wir eine entwicklerfreundliche und flexible Variante der Objektvernetzung haben wollen.

Außerdem ist anzumerken, dass wir mit Variante 3 zwar das Problem des reflektiven Zugriffs gelöst haben, allerdings ist völlig unklar ob und wie die Magie hinter Injector.inject() funktioniert. Dazu werde ich im nächsten Teil dieses Blogs eine Lösung skizzieren.

Zusammenfassend kann jedoch festgehalten werden, dass durchaus Lösungen existieren, DI zu realisieren, ohne illegale Zugriffe über die dunklen Pfade der Reflektion API gehen zu müssen. Will man ganz auf reflektiven Zugriff verzichten, bieten sich auch dazu Möglichkeiten wie es Frameworks wie Dagger auch im produktiven Einsatz beweisen.

Kontaktieren Sie uns jetzt

Engel & Völkers Technology
E-Mail
Zurück
Kontakt
Tragen Sie hier Ihre Kontaktdaten ein
Vielen Dank für Ihre Nachricht. Wir werden uns umgehend um Ihr Anliegen kümmern und uns schnellstmöglich bei Ihnen melden.

Ihr Engel & Völkers Team
Anrede
  • Herr
  • Frau
Absenden

Folgen Sie uns auf Social Media


Array
(
[EUNDV] => Array
(
[67d842e2b887a402186a2820b1713d693dd854a5_csrf_offer-form] => MTM5MjE5NzU3NkJ4d29xancwTDVhZWFIRzEycXAxcW9SdElHdVBqMTdV
[67d842e2b887a402186a2820b1713d693dd854a5_csrf_contact-form] => MTM5MjE5NzU3NnlHcUR0Y2VlTXVPUndLMHZkMW9zMnRmRlgxaUcwaFVG
)
)