Spring Framework Core

Diese Seite bleibt auch ohne JavaScript lesbar. Suche und Buttons sind Zusatzkomfort.

Spring Core: DI, IoC, Beans und AOP

Das Fundament: ApplicationContext, Bean Lifecycle, Proxies, Querschnittsfunktionen und testbare Architektur.

IoC Container: Objekte werden nicht gesucht, sondern injiziert @ConfigurationBean Definitions ApplicationContexterstellt, verdrahtet, verwaltetScopes & Lifecycle Service@Bean RepoProxy Fachlich: Use Case bleibt testbar. Technisch: Konstruktor-Injektion macht Abhängigkeiten sichtbar.
1. IoC und Dependency Injection richtig verstehen

In klassischen Legacy-Anwendungen erzeugen Klassen oft ihre Abhängigkeiten selbst: new Service(), ServiceLocator.lookup(), statische Singletons oder direkte JNDI-Zugriffe. Dadurch entstehen versteckte Kopplungen. Spring dreht die Kontrolle um: Der Container erzeugt und verdrahtet Objekte. Die Klasse beschreibt ihre benötigten Abhängigkeiten über den Konstruktor.

Konstruktor-Injektion ist im Enterprise-Alltag meist die robusteste Variante: Pflichtabhängigkeiten sind sichtbar, Tests können einfache Fakes übergeben, und zirkuläre Abhängigkeiten fallen früh auf.

Schlechter Ausgangspunkt: versteckte Kopplung
public class InvoiceService {
    private final TaxClient taxClient = new TaxClient("https://tax.internal");
    private final InvoiceRepository repository = new OracleInvoiceRepository();

    public Invoice createInvoice(Order order) {
        Money tax = taxClient.calculate(order.total());
        Invoice invoice = Invoice.from(order, tax);
        repository.save(invoice);
        return invoice;
    }
}
Besser: explizite Abhängigkeiten über Konstruktor
@Service
public class InvoiceService {
    private final TaxClient taxClient;
    private final InvoiceRepository repository;

    public InvoiceService(TaxClient taxClient, InvoiceRepository repository) {
        this.taxClient = taxClient;
        this.repository = repository;
    }

    public Invoice createInvoice(Order order) {
        Money tax = taxClient.calculate(order.total());
        Invoice invoice = Invoice.from(order, tax);
        repository.save(invoice);
        return invoice;
    }
}
2. Bean Lifecycle und Scopes

Singleton bedeutet in Spring: ein Bean-Objekt pro ApplicationContext, nicht global pro JVM. Deshalb müssen Singleton-Beans threadsicher entworfen werden. Mutable Felder mit Request-Zustand gehören nicht in Singleton-Services.

Prototype-Beans werden bei jeder Anfrage neu erzeugt, lösen aber selten Enterprise-Probleme. Für Web-Zustand gibt es Request- oder Session-Scopes; für fachliche Verarbeitung sollte Zustand möglichst über Methodenparameter laufen.

Problem: Zustand im Singleton-Service
@Service
class PaymentService {
    private String currentUser; // gefährlich: geteilter Zustand zwischen Requests

    public Receipt pay(Command command) {
        this.currentUser = command.user();
        return process(command);
    }
}
Besser: zustandsarm und thread-safe
@Service
class PaymentService {
    public Receipt pay(Command command, UserId userId) {
        return process(command, userId);
    }
}
3. AOP und Proxies

Spring nutzt Proxies für viele Querschnitte: Transaktionen, Security-Methodenregeln, Caching, Retry oder eigene Aspekte. Wichtig ist: Der Aufruf muss über den Proxy laufen. Self-Invocation innerhalb derselben Klasse umgeht häufig den Proxy und damit die Annotation.

AOP eignet sich für technische Querschnitte. Fachlogik sollte nicht in Aspekten versteckt werden, weil sie sonst schwer sichtbar, schwer testbar und schwer nachvollziehbar wird.

AOP-Merksatz am Beispiel Transaktion
@Service
class OrderApplicationService {
    @Transactional
    public void placeOrder(PlaceOrder command) {
        // Transaktion beginnt beim Proxy-Aufruf von außen
    }

    public void wrong() {
        placeOrder(new PlaceOrder(...)); // Self-Invocation: proxy kann umgangen werden
    }
}

Enterprise-Prüffragen

  • Konstruktor-Injektion statt verstecktem Lookup?
  • Services zustandsarm und thread-safe?
  • Keine Fachlogik in AOP-Aspekten versteckt?
  • Self-Invocation bei @Transactional vermieden?