CodeGym /Kurse /Spring Core /Bean name, Alias und Bean-Suche

Bean name, Alias und Bean-Suche

Spring Core
Level 4, Lektion 1
Verfügbar

1. Namen und Typen im Container

Spring unterscheidet Beans nicht nur nach Typ, sondern auch nach Name. Solange die Applikation klein ist, scheint der Typ zu reichen: „Gib mir OrderPlacementService.class, danke sehr". Aber sobald das System auch nur ein bisschen komplexer wird, kommt das Unangenehme ans Licht: Derselbe Typ kann mehrmals vorkommen, und manchmal braucht man eine bestimmte Instanz, nicht „irgendetwas Passendes". Genau dann kommen Namen ins Spiel.

Die Containergrenze haben wir schon gezogen: Nicht jedes Objekt der Applikation muss eine Bean sein. Jetzt ist es wichtig zu verstehen, wie der Container diejenigen unterscheidet, die tatsächlich drin sind.

Stell dir vor, der ApplicationContext ist ein großer Schrank, und die Beans sind die Schachteln darin. Der Typ ist wie ein Aufkleber „Werkzeug" oder „Dokumente", und der Name ist die konkrete Beschriftung auf der Schachtel: „Hammer", „Zange", „Ausweis". Nach Typ kannst du sagen: „Gib mir irgendein Werkzeug", aber manchmal brauchst du eben den Hammer und nicht „irgendetwas Schweres".

In Spring sieht das so aus:

  • lookup by type – Suche nach Typ (Klasse/Interface);
  • lookup by name – Suche nach Name (String);
  • und dann gibt es noch alias – ein alternativer Name für dieselbe Bean.

Der Kerngedanke hier: Im Alltagscode versucht Spring, Abhängigkeiten selbst einzusetzen per DI, und explizites getBean(...) ist eher ein Werkzeug für den Einstiegspunkt und die Diagnose. Aber um überhaupt ein Problem zu diagnostizieren, muss man verstehen, wie der Container denkt: in Typen und Namen.

2. Bean name: der Name eines Objekts im Container

Bean name ist der String-Bezeichner einer Bean innerhalb des Containers. Er sorgt dafür, dass der Container adressiert sagen kann: „Dieses Objekt ist scenarioRunner", „Dieses ist orderStore". Intern ähnelt Spring dabei einem Dictionary: Schlüssel → Wert, wobei der Schlüssel der Name und der Wert die Bean ist (genauer: ihre Definition und/oder Instanz).

Wichtig, nicht zu verwechseln: Der Bean-Name muss nicht mit dem Klassennamen übereinstimmen. Die Klasse kann InMemoryOrderStore heißen, die Bean aber orderStore. Das ist normal: Der Name spiegelt typischerweise die Rolle wider, nicht die technische Implementierung. Die Rolle ist eine – „Bestellspeicher" –, die Implementierung kann sich ändern (und wird sich später ändern).

Noch ein Punkt, der Einsteiger auf Anhieb erwischt: Der Name ist nicht „nur zur Schönheit". Er nimmt wirklich am Leben des Containers teil. Wenn du context.getBean("orderStore") aufrufst, sucht Spring genau nach diesem Namen. Ist der Name falsch, wird der Container nicht „raten", was du meintest. Er sagt einfach: „Nicht gefunden." Und hat recht.

Um die Intuition zu festigen, hilft dieses Mini-Schema:

flowchart LR
  subgraph ApplicationContext["ApplicationContext (sehr vereinfacht)"]
    N1["bean name: orderStore"] --> D1["BeanDefinition"] --> I1["instance: InMemoryOrderStore"]
    N2["alias: mainOrderStore"] --> D1
  end

  T["lookup by type: OrderStore"] --> N1

Das Schema ist bewusst vereinfacht, aber die Idee stimmt: Name und Alias führen zur selben Definition, und der Typ führt zu einem oder mehreren Kandidaten, für die dieser Typ passt.

3. Bean-Name in AppConfig

Wir haben noch kein Component Scanning, daher registrieren wir Beans im Projekt ContextFlow über eine Konfigurationsklasse (nennen wir sie der Einfachheit halber AppConfig). Hier gilt eine sehr einfache Regel: Wenn eine Bean über eine @Bean-Methode deklariert wird, ist der Bean-Name standardmäßig gleich dem Methodennamen. Das ist keine Magie, sondern eine praktische Konvention.

Schauen wir uns einen kleinen Ausschnitt der Konfiguration an. Betrachte es als Fragment innerhalb von AppConfig:

import org.springframework.context.annotation.Bean;

// Wichtig: Die Methode ist mit @Bean markiert, Spring registriert das Ergebnis also als Bean im Container.
// Hier ist der Bean-Name standardmäßig = Methodenname (orderStore).
@Bean
public OrderStore orderStore() {
    // Wir erstellen eine konkrete Implementierung, geben sie aber als Interface/Abstraktion zurück.
    return new InMemoryOrderStore();
}

Der Bean-Name hier ist "orderStore" – genau wie die Methode. Das ist praktisch: Du liest die Konfiguration als „Im Container gibt es orderStore", nicht als „Im Container liegt etwas mit einem langen Implementierungsnamen".

Nun der praktische Punkt. Sobald die Bean einen Namen hat, kannst du sie aus dem Kontext per Name holen:

// Bean per Name holen und Typ sofort prüfen (fail-fast).
OrderStore store = context.getBean("orderStore", OrderStore.class);

// Schnell prüfen, welche Implementierung hinter dem Interface steckt.
System.out.println(store.getClass().getSimpleName()); // InMemoryOrderStore

Beachte: Wir verwenden die Variante getBean(name, type). Sie ist sicherer als getBean(name) ohne Typ, weil Spring zusätzlich prüft: Liegt unter diesem Namen wirklich ein Objekt des gewünschten Typs?

Manchmal muss der Name explizit angegeben werden. Die Methode könnte z.B. orderStore() heißen, du willst die Bean im Container aber mainOrderStore nennen. Das geht so:

import org.springframework.context.annotation.Bean;

// Bean-Name explizit setzen (der Methodenname wird dann NICHT mehr als Standard verwendet).
@Bean("mainOrderStore")
public OrderStore orderStore() {
    return new InMemoryOrderStore();
}

Jetzt funktioniert die Suche nach "orderStore" nicht mehr (sofern kein Alias gesetzt wurde), aber nach "mainOrderStore" schon. Und hier bekommt ein Einsteiger ein wichtiges Gefühl: „Okay, der Container lebt wirklich mit Namen – das sind keine bloßen Kommentare in der Doku."

4. Alias: der zweite Name einer Bean

Ein Alias ist ein zusätzlicher Name, der auf dieselbe Bean zeigt. Das ist keine „zweite Bean" und keine „zweite Instanz". Es ist einfach ein Spitzname für ein bereits vorhandenes Objekt. Wie im Leben: ein Mensch, aber im Telefonbuch kann er als „Alexander" und „Alex aus dem DevOps-Team" eingetragen sein.

In @Bean können mehrere Namen auf einmal angegeben werden. Der erste gilt als Hauptname, die anderen sind Aliases. Machen wir das z.B. für ScenarioRunner:

import org.springframework.context.annotation.Bean;

// Erster Name ist der Hauptname, die anderen sind Aliases.
// Alle Namen zeigen auf dieselbe BeanDefinition und (bei Singleton) auf dieselbe Instanz.
@Bean(name = {"scenarioRunner", "mainScenarioRunner"})
public ScenarioRunner scenarioRunner(OrderPlacementService service) {
    // OrderPlacementService wird vom Container eingesetzt (DI) – wir suchen ihn hier nicht manuell.
    return new ScenarioRunner(service);
}

Jetzt führen beide Namen zum selben Objekt:

ScenarioRunner a = context.getBean("scenarioRunner", ScenarioRunner.class);
ScenarioRunner b = context.getBean("mainScenarioRunner", ScenarioRunner.class);

// Prüfen, dass es dasselbe Objekt ist (und nicht zwei verschiedene Kopien).
System.out.println(a == b); // true

Das ist eine wunderbare Brücke zur nächsten Lektion über Spring Singleton: Du siehst mit eigenen Augen, dass du aus einem Container dieselbe Instanz bekommst. Der Alias verstärkt die Beobachtbarkeit hier: zwei verschiedene Schlüssel, aber dasselbe Objekt.

Wozu braucht man Aliases überhaupt? In echten Projekten tauchen sie auf, wenn du eine Bean umbenennst (oder die Konfiguration migrierst), aber die Rückwärtskompatibilität wahren willst: Der alte Name wird noch irgendwo in der Konfiguration oder im Legacy-Code verwendet, der neue ist schon „korrekter". Ein Alias ermöglicht einen sanften Übergang.

5. Lookup nach Typ: wann es schön ist und wann es murrt

Die Suche nach Typ ist die angenehmste Option fürs Gehirn: Du denkst nicht an Strings, sondern an Klassen und Interfaces. In einer kleinen Applikation klingt das wie „Na klar will ich ScenarioRunner – der ist ja der Einzige." Und das ist wirklich ein normaler Ansatz, solange es genau einen Kandidaten gibt.

Hier ist unser typischer Bootstrap-Code – ein Fragment aus main(), das die Start-Bean nach Typ holt:

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

// Kontext auf Basis der Konfigurationsklasse starten.
try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
    // Bean nach Typ holen: funktioniert, solange es genau einen Kandidaten gibt.
    ScenarioRunner runner = context.getBean(ScenarioRunner.class);

    // Anwendungsszenario starten.
    runner.run();
}

Der Vorteil dieses Lookups: Er hängt kaum von Namen ab. Du kannst die Bean umbenennen (oder die @Bean-Methode) – und der Code funktioniert trotzdem, solange sich Typ und Kandidatenanzahl nicht geändert haben.

Aber es gibt eine Kehrseite: Die Suche nach Typ klappt nur, wenn Spring eindeutig wählen kann. Falls der Container zwei Beans hat, die zu einem Interface passen, hört getBean(Interface.class) auf, „praktisch" zu sein, und fängt an zu murren.

Noch bevor mehrere Implementierungen auftauchen, ist es gut, einen einfachen Diagnose-Trick zu kennen: Der ApplicationContext kann sagen, welche Namen für einen bestimmten Typ registriert sind. Das ist eine gute Taschenlampe im Dunkeln:

// Schauen, welche Bean-Namen zum angegebenen Typ passen.
String[] names = context.getBeanNamesForType(OrderStore.class);

// Praktisch für die Diagnose, wenn die Typ-Suche mehrdeutig geworden ist.
System.out.println(String.join(", ", names)); // z.B.: orderStore

Solange es nur einen OrderStore gibt, siehst du einen Namen. Wenn die Kandidaten mehr werden und der Container zu murren beginnt, weißt du zumindest: „Aha, ich habe wirklich zwei Beans dieses Typs – hier sind ihre Namen."

6. Lookup nach Name

Die Suche nach Name ist dann sinnvoll, wenn du eine Bean als konkrete Instanz im Container ansprechen möchtest, nicht nur als „Objekt eines bestimmten Typs". Namen sind besonders nützlich bei der Diagnose, beim App-Start und an Stellen, wo du die Komponentenwahl bewusst steuerst.

Die einfachste Variante – getBean(String) – gibt ein Object zurück. Sie funktioniert, verleitet Einsteiger aber leicht zu unnötigen Casts und Fehlern. Daher gewöhne dich im Lernprojekt gleich an die sicherere Form: getBean(String, Class).

Vergleiche beide Varianten:

// Einfache Variante: liefert Object (man fängt leicht an, "Glücks-Casts" zu machen).
Object raw = context.getBean("scenarioRunner");

// Schnelle Diagnose: Was hat der Container genau zurückgegeben (Klasse/Proxy usw.).
System.out.println(raw.getClass().getSimpleName()); // ScenarioRunner (oder später Proxy/Wrapper)

und die sicherere:

// Sichere Variante: Spring prüft sofort, ob unter dem Namen der richtige Typ liegt.
ScenarioRunner runner = context.getBean("scenarioRunner", ScenarioRunner.class);

// Danach arbeiten wir mit der typisierten Referenz.
runner.run(); // Szenario starten

In der zweiten Variante prüft Spring den Typ direkt an Ort und Stelle. Hast du Name oder Typ falsch angegeben, bekommst du sofort eine verständliche Container-Fehlermeldung – und nicht irgendwo auf dem Weg eine zufällige ClassCastException.

Wichtig ist, die Grenze zu wahren: Lookup nach Name ist kein „neuer Stil für Geschäftslogik". Es ist eher ein punktuelles Werkzeug, wie ein Schraubenzieher. Damit kannst du gut eine Schraube festziehen, aber schlecht Suppe kochen.

Lookup nach Name + Typ

Wenn du einen manuellen Lookup machst – z.B. in main(), im ScenarioRunner als Einstiegspunkt oder bei der Diagnose –, halte dich am besten an den „goldenen Mittelweg": Gib immer Name und Typ an. Dann wird der Code selbstdokumentierend: Man sieht sofort, welche Bean erwartet wird und was davon gewünscht ist.

Ein kurzes Beispiel, das gleichzeitig drei Dinge zeigt – Name, Alias und Typsicherheit:

// Bean per Alias holen (sofern Alias registriert ist) und gleichzeitig den Typ prüfen.
ScenarioRunner runner = context.getBean("mainScenarioRunner", ScenarioRunner.class);

System.out.println(runner.getClass().getSimpleName()); // ScenarioRunner

// Danach wie ein normales Objekt verwenden.
runner.run(); // Szenario starten

Ist der Alias nicht registriert, teilt der Container ehrlich mit, dass keine Bean mit diesem Namen gefunden wurde. Liegt unter diesem Namen ein anderer Typ, sagt der Container ebenfalls ehrlich, dass der Typ nicht passt. Und du musst nicht rätseln, „warum es irgendwo tief drin abgestürzt ist."

Im positiven Sinne ist das wie eine Bestellung im Café. Man kann sagen „einen Kaffee bitte" (lookup by type), oder man sagt „einen Latte 300 ml auf Hafer" (lookup by name + type). Die zweite Variante dauert ein paar Sekunden länger, aber die Chance, das Falsche zu bekommen, ist geringer.

7. Service Locator und getBean() in der Geschäftslogik

Irgendwann macht ein Einsteiger eine Entdeckung: „Oh! Ich kann den Container jederzeit nach allem fragen. Also muss ich keine Abhängigkeiten mehr über den Konstruktor übergeben." Und genau hier fängt DI an, still in der Ecke zu weinen, denn du landest unbemerkt beim Antipattern Service Locator.

Ein schlechtes Beispiel sieht ungefähr so aus – ein Fragment, das man besser nie ins Code-Review bringt:

public class OrderPlacementService {

    private final ApplicationContext context;

    public OrderPlacementService(ApplicationContext context) {
        // Abhängigkeit vom Container: Die Klasse "kennt" jetzt Spring und kann alles mögliche holen.
        this.context = context;
    }

    public void placeOrder(CreateOrderCommand cmd) {
        // Versteckte Abhängigkeit: Aus der Methodensignatur und den Feldern ist nicht ersichtlich, dass AuditWriter benötigt wird.
        AuditWriter auditWriter = context.getBean(AuditWriter.class);

        // Irgendeine Geschäftslogik...
        auditWriter.write("Order created");
    }
}

Das Problem ist nicht, dass Spring das „verbietet". Technisch funktioniert es. Das Problem ist ein anderes: Du versteckst Abhängigkeiten wieder. Am Klassencode ist nicht mehr zu erkennen, dass er AuditWriter benötigt. Solchen Code zu testen ist schwieriger, die Implementierung zu wechseln ist schwieriger, und Wiring-Fehler zeigen sich zu spät und auf viel unangenehmere Weise.

Der gute Ansatz – im Geiste dessen, was wir bereits mit reinem Java und DI gemacht haben – ist, Abhängigkeiten als Felder zu halten, die von außen übergeben werden, statt sie unterwegs zu suchen:

public class OrderPlacementService {

    private final AuditWriter auditWriter;

    public OrderPlacementService(AuditWriter auditWriter) {
        // Explizite Abhängigkeit: Sofort ersichtlich, dass der Service einen AuditWriter braucht.
        this.auditWriter = auditWriter;
    }
}

Der Spring-Container ist genau dafür da: den Abhängigkeitsgraphen vorab zusammenzubauen – und nicht dafür, dass du Abhängigkeiten mitten in einer Geschäftsmethode „jagst", wie Pokémons im Gras.

8. Diagnose: Beans im Kontext

Wenn Spring beim Start abstürzt oder du nicht verstehst, warum getBean(...) das gewünschte Objekt nicht findet, möchtest du am liebsten irgendwie „in den Container schauen". Gute Nachricht: Für Lernzwecke und Diagnosen ist das völlig in Ordnung – ohne die Applikation in ein containerabhängiges Chaos zu verwandeln.

Der einfachste Weg: Die Namen der registrierten Bean Definitions ausgeben:

import java.util.Arrays;

// context ist bereits erstellt (z.B. via AnnotationConfigApplicationContext).
String[] names = context.getBeanDefinitionNames();

// Die Liste ist oft lang: Sie enthält deine Beans und die Spring-Infrastruktur.
System.out.println(Arrays.toString(names)); // [appConfig, orderStore, scenarioRunner, ...]

Diese Ausgabe sieht oft lang aus, weil Spring selbst Infrastruktur-Beans hinzufügt. Keine Panik: Uns interessiert das, was du explizit hinzugefügt hast (z.B. orderStore, scenarioRunner), und das, was mit deinem aktuellen Problem zusammenhängt.

Eine weitere nützliche Sache: Das Vorhandensein einer Bean per Name prüfen. Das ist praktisch, wenn du dir nicht sicher bist, wie sie genau heißt:

// Vorhandensein einer Bean per Name prüfen (nützlich für Diagnose und temporäre Checks).
boolean exists = context.containsBean("orderStore");

System.out.println(exists); // true

Im Lernprojekt kann das als diagnostischer „Abtastcode" verwendet werden, der danach entfernt wird. In echten Applikationen lässt man solche Checks besser in Tests oder isolierter Diagnose – nicht in der Geschäftslogik.

9. Typische Fehler bei Bean name und Alias

Fehler Nr. 1: Bean-Name und Klassenname verwechseln.
Ein Einsteiger sieht die Klasse InMemoryOrderStore und versucht getBean("InMemoryOrderStore"). Der Container lebt mit Bean-Namen, nicht mit Klassennamen. In unserer Konfiguration kommt der Name aus der @Bean-Methode – denk also eher „Bean orderStore vom Typ OrderStore", nicht „Klasse InMemoryOrderStore".

Fehler Nr. 2: getBean("name") ohne Typangabe verwenden und dann einen „Glücks-Cast" machen.
Das führt zu unangenehmen ClassCastExceptions und Situationen wie „Es ist nicht da abgestürzt, wo ich es erwartet hatte." Im Lernprojekt gewöhne dich sofort daran: Wenn du per Name gehst, dann mach getBean("name", SomeType.class). Das gibt dir fail-fast und menschlichere Fehlermeldungen.

Fehler Nr. 3: Denken, dass ein Alias eine zweite Instanz erstellt.
Ein Alias ist keine „Bean-Kopie" und kein „weiteres Objekt". Er ist ein zweiter Name für dieselbe Bean. Deshalb ergibt der Vergleich a == b beim Zugriff über Hauptname und Alias true – und das ist normales Verhalten. Hast du zwei verschiedene Objekte erwartet, hast du Alias mit einer anderen BeanDefinition verwechselt.

Fehler Nr. 4: context.getBean(...) überall in Services verstreuen, weil „es so einfacher ist".
Kurzfristig klingt das wirklich einfacher: „Muss nicht nachdenken, was ich übergebe." Aber schon nach drei, vier Klassen hat man versteckte Abhängigkeiten, zerbrechliche Tests und Wiring-Probleme, die sich zu spät zeigen. Spring und DI wurden für genau das Gegenteil gedacht: Abhängigkeiten sollen offensichtlich sein, nicht dynamisch gesucht werden.

Fehler Nr. 5: Eine @Bean-Methode umbenennen und sich wundern, warum der Name-Lookup kaputt ist.
Wenn du dich auf den Bean-Namen als String verlassen hast, ändert das Umbenennen der Methode scenarioRunner() in runner() auch den Standard-Bean-Namen. Dann funktioniert getBean("scenarioRunner", ...) nicht mehr. Ist der Name ein Vertrag, setze ihn explizit (@Bean("scenarioRunner")) oder verwende während der Migration einen Alias.

1
Aufgabe
Spring Core, Level 4, Lektion 1
Gesperrt
Ein Bean, zwei Wege zum Lookup
Ein Bean, zwei Wege zum Lookup
1
Aufgabe
Spring Core, Level 4, Lektion 1
Gesperrt
Alias für einen Bean
Alias für einen Bean
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION