CodeGym /Kurse /JAVA 25 SELF /Reflection und Dependency Injection

Reflection und Dependency Injection

JAVA 25 SELF
Level 62, Lektion 4
Verfügbar

1. Serialisierung mit Reflection

Sie wissen bereits, dass Serialisierung der Prozess ist, ein Objekt in einen Bytestrom oder eine textuelle Darstellung umzuwandeln. In Java gibt es die Standardserialisierung (Serializable), aber häufiger verwendet man JSON/XML-Formate über Bibliotheken wie Jackson und Gson.

Warum braucht man hier Reflection?
Um ein Objekt zu serialisieren, muss man seine Felder und deren Werte kennen. Felder können private sein, und gewöhnlicher Code kommt nicht an sie heran – Reflection hingegen schon. Daher traversieren JSON‑Bibliotheken die Struktur des Objekts dynamisch und lesen/schreiben Felder über Field und Method.

Beispiel: einfache Serialisierung eines Objekts in einen String

Datenklasse:

public class Person {
    private String name;
    private int age;
    private boolean active;

    public Person(String name, int age, boolean active) {
        this.name = name;
        this.age = age;
        this.active = active;
    }
}

Einfachster Serializer mit Reflection:

import java.lang.reflect.Field;

public class SimpleSerializer {
    public static String serialize(Object obj) {
        StringBuilder sb = new StringBuilder();
        Class<?> clazz = obj.getClass();
        sb.append(clazz.getSimpleName()).append("{");
        Field[] fields = clazz.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            field.setAccessible(true); // Private Felder zugänglich machen
            try {
                sb.append(field.getName()).append("=")
                  .append(field.get(obj));
            } catch (IllegalAccessException e) {
                sb.append(field.getName()).append("=<?>");
            }
            if (i < fields.length - 1) sb.append(", ");
        }
        sb.append("}");
        return sb.toString();
    }
}

Verwendung:

Person p = new Person("Alice", 30, true);
System.out.println(SimpleSerializer.serialize(p));
Person{name=Alice, age=30, active=true}

Wie funktioniert das?

  • Wir holen die deklarierten Felder über getDeclaredFields().
  • Wir machen sie über setAccessible(true) zugänglich.
  • Wir lesen Namen und Werte der Felder und bauen den String zusammen.

Einschränkungen des Beispiels: keine Behandlung verschachtelter Objekte, Collections, Arrays und zyklischer Referenzen – dies ist nur eine Demonstration des Prinzips.

Wie machen es ausgereifte Bibliotheken?

Jackson/Gson können mit verschachtelten Objekten und Collections arbeiten, berücksichtigen Annotationen (@JsonIgnore, @SerializedName), Datumsformate und vieles mehr – all das basiert auf Reflection.

Beispiel mit Jackson:

import com.fasterxml.jackson.databind.ObjectMapper;

Person p = new Person("Bob", 25, false);
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(p);
// {"name":"Bob","age":25,"active":false}

2. Dependency Injection (DI) und Reflection

Dependency Injection ist ein Muster, bei dem Abhängigkeiten von außen „injiziert“ werden, statt innerhalb der Klasse erzeugt zu werden. Das macht den Code flexibel, testbar und erweiterbar. In Java übernehmen das Frameworks Spring, Guice, Dagger, indem sie Injektionspunkte mit Annotationen wie @Autowired, @Inject markieren.

Warum ist hier Reflection nötig?
Ein DI‑Container muss Felder/Konstruktoren finden, Annotationen lesen und zur Laufzeit Instanzen erzeugen – das geschieht über Class/Constructor/Field-APIs.

Beispiel: Mini‑DI mit Reflection

Injektions-Annotation:

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Inject {
}

Klassen mit Abhängigkeit:

public class Service {
    public void doWork() {
        System.out.println("Service is working!");
    }
}

public class Client {
    @Inject
    private Service service;

    public void useService() {
        service.doWork();
    }
}

Mini-Container:

import java.lang.reflect.*;

public class MiniDIContainer {
    // Erzeugt ein Objekt anhand der Klasse und injiziert Abhängigkeiten in Felder mit @Inject
    public static Object createObject(Class<?> clazz) throws Exception {
        Object obj = clazz.getDeclaredConstructor().newInstance();

        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(Inject.class)) {
                Object dependency = createObject(field.getType()); // rekursiv
                field.setAccessible(true);
                field.set(obj, dependency);
            }
        }
        return obj;
    }
}

Verwendung:

public class Main {
    public static void main(String[] args) throws Exception {
        Client client = (Client) MiniDIContainer.createObject(Client.class);
        client.useService(); // Service is working!
    }
}

Wie funktioniert das? Der Container findet Felder mit @Inject, erzeugt Abhängigkeiten anhand ihres Typs und setzt sie per Reflection in private Felder.

Wichtig: Das ist ein vereinfachtes Schema. Reale DI‑Container unterstützen Scopes, Singletons, Konfiguration, Proxys, die Behandlung zyklischer Abhängigkeiten u. v. m.

3. Dynamische Proxys

Ein Proxy ist ein „Stellvertreter“-Objekt, das Aufrufe abfängt und Verhalten hinzufügt: Logging, Sicherheit, Transaktionen usw. In Java übernimmt das java.lang.reflect.Proxy zusammen mit InvocationHandler. Grundlage vieler Möglichkeiten von Spring AOP, Mocks in Mockito usw.

Beispiel: Logging-Proxy

public interface HelloService {
    void sayHello(String name);
}
public class HelloServiceImpl implements HelloService {
    public void sayHello(String name) {
        System.out.println("Hello, " + name + "!");
    }
}
import java.lang.reflect.*;

public class LoggingProxy {
    @SuppressWarnings("unchecked")
    public static <T> T createProxy(T target, Class<T> iface) {
        return (T) Proxy.newProxyInstance(
            iface.getClassLoader(),
            new Class<?>[]{iface},
            new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("Methodenaufruf: " + method.getName());
                    return method.invoke(target, args);
                }
            }
        );
    }
}

Verwendung:

HelloService original = new HelloServiceImpl();
HelloService proxy = LoggingProxy.createProxy(original, HelloService.class);

proxy.sayHello("World");
Methodenaufruf: sayHello
Hello, World!

Wie funktioniert das? Proxy.newProxyInstance erzeugt ein Objekt, das das Interface implementiert, und alle Methodenaufrufe gehen an InvocationHandler.invoke, wo Sie beliebigen „querschnittlichen“ Code vor/nach der Delegation ausführen können.

4. Reale Szenarien und Einschränkungen

Wo wird Reflection „im Einsatz“ verwendet?

  • JUnit – findet Methoden mit @Test und ruft sie per Reflection auf.
  • Spring – erzeugt Beans, injiziert Abhängigkeiten, scannt Annotationen, generiert Proxys.
  • Jackson/Gson – serialisieren/deserialisieren und lesen sogar private Felder.
  • Hibernate – baut ORM‑Modelle anhand der Klassenstruktur, verwaltet Felder und Proxy‑Objekte.
  • Mockito – erstellt Mocks und fängt Aufrufe über Proxys ab.

Warum sollte man Reflection nicht immer verwenden?

  • Leistung. Im Allgemeinen langsamer als direkte Aufrufe (abgemildert durch Caching/Bytecode‑Generierung).
  • Sicherheit. Durchbricht die Kapselung; Zugriff auf private Daten.
  • Modularität ab Java 9+. Ohne explizites Öffnen von Paketen/Modulen kann eine InaccessibleObjectException auftreten.

5. Häufige Fehler

Fehler Nr. 1: Ignorieren von checked Exceptions. Reflection‑Methoden werfen NoSuchFieldException, IllegalAccessException, InvocationTargetException u. a. Behandeln Sie sie oder kapseln Sie sie in eigenen Exceptions.

Fehler Nr. 2: setAccessible(true) funktioniert nicht immer. Unter Java 9+ sind in modularen Anwendungen mögliche InaccessibleObjectException. Es sind JVM-/Modul‑Parameter (--add-opens) oder öffentliche APIs erforderlich.

Fehler Nr. 3: Zyklische Abhängigkeiten im DI. Bei naiver Rekursion (A hängt von B ab, und B von A) erhalten Sie StackOverflowError. Reale Container verfolgen den Abhängigkeitsgraphen und lösen Zyklen mit speziellen Techniken.

Fehler Nr. 4: Unvollständige Serialisierung von Objekten. Referenzfelder werden als ClassName@hash serialisiert, wenn kein rekursiver Durchlauf erfolgt. Für eine korrekte Serialisierung sind die Behandlung verschachtelter Objekte/Collections und ein Schutz vor Zyklen nötig.

Fehler Nr. 5: Leistungsverlust. Häufige reflektive Operationen (in Schleifen, auf Hot Paths) werden zum Engpass. Verwenden Sie Caching von Field/Method, Bytecode‑Generierung, MethodHandle oder Annotation Processing.

Fehler Nr. 6: Verletzung der Kapselung. Änderungen an privaten Feldern per Reflection führen zu Fragilität und schwer auffindbaren Bugs. Bevorzugen Sie nach Möglichkeit öffentliche Verträge.

1
Aufgabe
JAVA 25 SELF, Level 62, Lektion 4
Gesperrt
Automatische Montage von Robotern mit intelligenten Komponenten 🤖
Automatische Montage von Robotern mit intelligenten Komponenten 🤖
1
Aufgabe
JAVA 25 SELF, Level 62, Lektion 4
Gesperrt
Spionage: Abfangen von Nachrichten im Dekodierer 🕵️
Spionage: Abfangen von Nachrichten im Dekodierer 🕵️
1
Umfrage/Quiz
Reflexion, Level 62, Lektion 4
Nicht verfügbar
Reflexion
Reflexion und dynamische Möglichkeiten
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION