Spring Boot Basic – Tag 2 von 10
Von Elyndra Valen, Senior Entwicklerin bei Java Fleet Systems Consulting


Spring Container

Tag 2: Der Spring Container & Dependency Injection

Spring Boot Basic – Tag 2 von 10
Von Elyndra Valen, Senior Entwicklerin bei Java Fleet Systems Consulting


📍 Deine Position im Kurs

TagThemaStatus
✅ 1Erste REST APIAbgeschlossen
2Spring Container & DI + CRUD👉 DU BIST HIER!
3@Controller & Thymeleaf BasicsNoch nicht freigeschaltet
4Thymeleaf Forms & MVC-PatternNoch nicht freigeschaltet
5Konfiguration & LoggingNoch nicht freigeschaltet
6DI & AOP im DetailNoch nicht freigeschaltet
7Scopes in SpringNoch nicht freigeschaltet
8WebSocketsNoch nicht freigeschaltet
9JAX-RS in Spring BootNoch nicht freigeschaltet
10Integration & AbschlussNoch nicht freigeschaltet

📋 Voraussetzungen

Du brauchst:

  • ✅ Tag 1 abgeschlossen (REST API funktioniert)
  • ✅ Spring Boot läuft auf deinem Rechner
  • ✅ Grundverständnis von Interfaces in Java
  • ✅ Dein Projekt von Tag 1

Tag 1 verpasst?Hier geht’s zum Blogbeitrag Tag 1


⚡ Was du heute baust/lernst:

Gestern hast du eine REST API gebaut. Aber was passiert da eigentlich im Hintergrund? Heute schauen wir hinter die Kulissen von Spring Boot und verstehen den Spring Container – das Herz jeder Spring-Anwendung! Du lernst, wie Spring deine Objekte verwaltet und wie du Dependency Injection richtig einsetzt.


🎯 Dein Lernpfad heute:

Du arbeitest heute in mehreren aufbauenden Schwierigkeitsstufen. Arbeite in deinem eigenen Tempo durch die Schritte:

🟢 Grundlagen (Schritte 1-4)

Was du lernst:

  • Den Spring Container verstehen (ApplicationContext)
  • @Component, @Service, @Controller Annotations kennenlernen
  • Service-Layer erstellen und einbinden
  • Dependency Injection – alle drei Varianten verstehen
  • Constructor Injection implementieren

Ziel: Service-Layer funktioniert, Controller nutzt Service via Constructor Injection, API läuft weiter

🟡 Professional (Schritte 5-6)

Was du lernst:

  • Lombok @RequiredArgsConstructor für cleanen Code
  • Spring Container debuggen (Beans auslesen)
  • ContextController für Introspection
  • Bean Lifecycle verstehen (@PostConstruct, @PreDestroy)

Ziel: Production-Ready Code mit Lombok, vollständiges Verständnis des Spring Containers

🔵 Bonus: Tiefes Spring-Verständnis (Schritt 7)

Was du baust/lernst:

  • Verschiedene Bean Scopes ausprobieren
  • Custom Beans erstellen mit @Bean
  • Qualifiers bei mehreren Implementierungen
  • ApplicationContext API im Detail

Ziel: Expertenwissen über Spring’s Dependency Injection Mechanismus

💡 Tipp: Die Grundlagen (Schritte 1-4) sind essenziell für alle weiteren Tage – ohne Service-Layer und DI-Verständnis wirst du nicht weiterkommen. Professional (Schritt 5-6) ist sehr empfehlenswert, besonders Lombok macht deinen Code deutlich sauberer. Die Bonus-Features kannst du überspringen, wenn du schnell zu Tag 3 willst.


💻 Los geht’s!

🟢 GRUNDLAGEN

Schritt 1: Was ist der Spring Container?

Gestern hast du das geschrieben:

@RestController
public class PersonController {
    // Controller funktioniert einfach!
}

Aber WER erstellt den Controller? WER verwaltet ihn?

Antwort: Der Spring Container (auch ApplicationContext genannt)!

Der Spring Container ist wie ein Restaurant-Manager:

Du kommst ins Restaurant (startest die App)
  ↓
Manager (Spring Container) organisiert alles:
  - Koch (Service)
  - Kellner (Controller)  
  ↓
Du bestellst Essen (HTTP Request)
  ↓
Manager koordiniert:
  Kellner → nimmt Bestellung
  Koch → bereitet zu (Service-Logik mit Daten im Speicher)

Spring Container:

  • Erstellt alle Objekte (Beans)
  • Verwaltet deren Lebenszyklus
  • Verbindet sie miteinander (Dependency Injection)
  • Räumt auf wenn’s vorbei ist

Wichtige Begriffe:

  • Bean: Ein von Spring verwaltetes Objekt
  • ApplicationContext: Der Spring Container selbst
  • Dependency Injection (DI): Spring gibt dir die Objekte, die du brauchst
  • Inversion of Control (IoC): Nicht du erstellst Objekte, Spring macht das

Schritt 2: Annotations verstehen – @Component & Co.

Spring erkennt deine Klassen durch Annotations.

Die wichtigsten Spring Annotations:

// Basis-Annotation: "Spring, bitte verwalte diese Klasse!"
@Component
public class MyComponent {
    // Allgemeine Spring-Komponente
}

// Service-Layer: Business-Logik
@Service
public class PersonService {
    // Rechnet, validiert, koordiniert
}

// Controller-Layer (MVC): HTTP-Handling mit Views
@Controller
public class PersonViewController {
    // Gibt HTML-Views zurück (z.B. mit Thymeleaf)
}

// REST Controller-Layer: HTTP-Handling mit JSON
@RestController
public class PersonController {
    // Gibt JSON zurück (kennst du schon!)
}

Was ist der Unterschied?

Technisch sind alle fast identisch – sie sagen Spring „verwalte mich!“

ABER: Die Namen zeigen die Rolle im System:

  • @Controller → Gibt HTML-Views zurück
  • @RestController → Gibt JSON zurück (kennst du von Tag 1)
  • @Service → Business-Logik
  • @Component → Alles andere

Best Practice: Nutze die spezifischen Annotations! Das macht deinen Code lesbarer.


Schritt 3: Projekt erweitern – Service-Layer hinzufügen

Wir bauen unsere Person-API aus Tag 1 um:

Aktuell (Tag 1):

Controller → Liste im Speicher

Neu (Tag 2):

Controller → Service → Liste im Speicher

Warum ein Service-Layer?

  • ✅ Trennung von Verantwortlichkeiten (Controller = HTTP, Service = Logik)
  • ✅ Wiederverwendbar (mehrere Controller können denselben Service nutzen)
  • ✅ Testbar (Service kann isoliert getestet werden)
  • ✅ Professional (so macht man das in echten Projekten!)

3.1 PersonService erstellen

Erstelle: src/main/java/com/example/helloworldapi/service/PersonService.java

package com.example.helloworldapi.service;

import com.example.helloworldapi.model.Person;
import net.datafaker.Faker;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

@Service
public class PersonService {
    
    private List<Person> persons = new ArrayList<>();
    
    public PersonService() {
        // Testdaten generieren
        Faker faker = new Faker(new Locale("de"));
        for (long i = 1; i <= 100; i++) {
            persons.add(new Person(
                i,
                faker.name().firstName(),
                faker.name().lastName(),
                faker.internet().emailAddress()
            ));
        }
    }
    
    public List<Person> getAllPersons() {
        return persons;
    }
    
    public Person getPersonById(Long id) {
        return persons.stream()
                .filter(p -> p.getId().equals(id))
                .findFirst()
                .orElse(null);
    }
    
    public Person createPerson(Person person) {
        person.setId((long) (persons.size() + 1));
        persons.add(person);
        return person;
    }
}

Was macht @Service?

  • Sagt Spring: „Das ist ein Service – verwalte ihn!“
  • Spring erstellt automatisch eine Instanz beim Start
  • Die Instanz ist singleton (nur eine pro App)

3.2 Controller anpassen

Jetzt muss der Controller den Service nutzen. Aber erstmal schauen wir uns an, wie das NICHT geht:

❌ FALSCH (manuell erstellen):

@RestController
public class PersonController {
    private PersonService service = new PersonService();  // NICHT SO!
}

Warum falsch?

  • Du umgehst den Spring Container
  • Jeder Controller erstellt eigene Service-Instanz
  • Ineffizient und nicht testbar

✅ RICHTIG (Dependency Injection): Spring gibt uns den Service – wir müssen ihn nur anfordern!


Schritt 4: Dependency Injection – Die drei Wege

Jetzt kommt der wichtige Teil: Wie bekommt der Controller den Service?

4.1 Field Injection (❌ Nicht empfohlen!)

@RestController
@RequestMapping("/api/persons")
public class PersonController {
    
    @Autowired
    private PersonService personService;  // Spring "injiziert" hier
    
    @GetMapping
    public List<Person> getAllPersons() {
        return personService.getAllPersons();
    }
}

Funktioniert, aber:

  • ❌ Schwer zu testen (braucht Reflection)
  • ❌ Kann nicht final sein (nicht Thread-safe)
  • ❌ Versteckte Dependency

4.2 Setter Injection (🤷 Manchmal okay)

@RestController
@RequestMapping("/api/persons")
public class PersonController {
    
    private PersonService personService;
    
    @Autowired
    public void setPersonService(PersonService personService) {
        this.personService = personService;
    }
    
    @GetMapping
    public List<Person> getAllPersons() {
        return personService.getAllPersons();
    }
}

Besser, aber:

  • ✅ Testbar
  • ❌ Kann immer noch nicht final sein
  • ❌ Optionale Dependency (kann null sein!)

4.3 Constructor Injection (✅ EMPFOHLEN!)

@RestController
@RequestMapping("/api/persons")
public class PersonController {
    
    private final PersonService personService;  // final = immutable!
    
    // Spring ruft diesen Konstruktor automatisch auf
    public PersonController(PersonService personService) {
        this.personService = personService;
    }
    
    @GetMapping
    public List<Person> getAllPersons() {
        return personService.getAllPersons();
    }
    
    @GetMapping("/{id}")
    public Person getPersonById(@PathVariable Long id) {
        return personService.getPersonById(id);
    }
}

Perfekt, weil:

  • final möglich (immutable, Thread-safe)
  • ✅ Einfach testbar (Service im Test übergeben)
  • ✅ Klare Dependencies (siehst du im Konstruktor)
  • ✅ Spring Best Practice!

Wichtig: Seit Spring 4.3 ist @Autowired beim Konstruktor optional, wenn die Klasse nur einen Konstruktor hat!

4.4 Controller vollständig umbauen

Ersetze deinen PersonController von Tag 1 mit dieser Version:

package com.example.helloworldapi.controller;

import com.example.helloworldapi.model.Person;
import com.example.helloworldapi.service.PersonService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/api/persons")
public class PersonController {
    
    private final PersonService personService;
    
    // Constructor Injection - Spring findet PersonService automatisch
    public PersonController(PersonService personService) {
        this.personService = personService;
    }
    
    @GetMapping
    public List<Person> getAllPersons() {
        return personService.getAllPersons();
    }
    
    @GetMapping("/{id}")
    public Person getPersonById(@PathVariable Long id) {
        return personService.getPersonById(id);
    }
    
    @PostMapping
    public Person createPerson(@RequestBody Person person) {
        return personService.createPerson(person);
    }
}

Teste es:

mvn spring-boot:run

curl http://localhost:8080/api/persons
curl http://localhost:8080/api/persons/1

🎉 Es funktioniert! Spring hat automatisch PersonService in PersonController injiziert!


🟡 PROFESSIONAL

Schritt 5: Lombok – Noch sauberer mit @RequiredArgsConstructor

Constructor Injection ist super, aber wir können es noch besser machen!

5.1 Lombok Dependency hinzufügen

In pom.xml einfügen:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

In NetBeans:

  • Rechtsklick auf Projekt → Clean and Build

5.2 Controller mit Lombok

Vorher (manuell):

@RestController
@RequestMapping("/api/persons")
public class PersonController {
    
    private final PersonService personService;
    
    public PersonController(PersonService personService) {
        this.personService = personService;
    }
    // ... methods ...
}

Nachher (mit Lombok):

@RestController
@RequestMapping("/api/persons")
@RequiredArgsConstructor  // Lombok generiert Konstruktor automatisch!
public class PersonController {
    
    private final PersonService personService;
    
    // Konstruktor wird automatisch generiert!
    
    @GetMapping
    public List<Person> getAllPersons() {
        return personService.getAllPersons();
    }
    
    @GetMapping("/{id}")
    public Person getPersonById(@PathVariable Long id) {
        return personService.getPersonById(id);
    }
}

Was macht @RequiredArgsConstructor?

  • Generiert automatisch einen Konstruktor
  • Für alle final Fields
  • Spring nutzt diesen Konstruktor für DI

Vorteil:

  • ✅ Weniger Boilerplate-Code
  • ✅ Bei neuen Dependencies automatisch angepasst
  • ✅ Standard in modernen Spring-Projekten

Schritt 6: Spring Container debuggen

Wie viele Beans hat Spring erstellt? Welche genau?

Lass uns einen Debug-Controller bauen!

6.1 ContextController erstellen

Erstelle: src/main/java/com/example/helloworldapi/controller/ContextController.java

package com.example.helloworldapi.controller;

import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/api/context")
public class ContextController {
    
    private final ApplicationContext context;
    
    public ContextController(ApplicationContext context) {
        this.context = context;
    }
    
    @GetMapping("/beans")
    public List<String> getAllBeans() {
        // Alle Beans im Context auslesen
        return Arrays.stream(context.getBeanDefinitionNames())
                .sorted()
                .collect(Collectors.toList());
    }
    
    @GetMapping("/my-beans")
    public List<String> getMyBeans() {
        // Nur unsere eigenen Beans
        return Arrays.stream(context.getBeanDefinitionNames())
                .filter(name -> name.contains("person") || 
                               name.contains("hello") || 
                               name.contains("context"))
                .sorted()
                .collect(Collectors.toList());
    }
    
    @GetMapping("/bean-count")
    public int getBeanCount() {
        return context.getBeanDefinitionNames().length;
    }
}

6.2 Testen:

# Starte die App
mvn spring-boot:run

# Alle Beans anzeigen (>100!)
curl http://localhost:8080/api/context/beans

# Nur unsere Beans
curl http://localhost:8080/api/context/my-beans

# Wie viele Beans insgesamt?
curl http://localhost:8080/api/context/bean-count

Ergebnis von /my-beans:

[
  "contextController",
  "helloController",
  "personController",
  "personService"
]

🎉 Spring hat all unsere Klassen automatisch erstellt!

Ergebnis von /bean-count:

137

Spring Boot hat 137 Beans erstellt – automatisch! Das sind alle Spring Boot Auto-Konfigurationen (Tomcat, Jackson, etc.)


Schritt 7: Bean-Lifecycle verstehen

Wann erstellt Spring die Beans?

Erweitere PersonService mit Lifecycle-Methoden:

package com.example.helloworldapi.service;

import com.example.helloworldapi.model.Person;
import net.datafaker.Faker;
import org.springframework.stereotype.Service;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

@Service
public class PersonService {
    
    private List<Person> persons = new ArrayList<>();
    
    public PersonService() {
        System.out.println("1️⃣ PersonService Konstruktor wird aufgerufen");
    }
    
    @PostConstruct  // Wird NACH dem Konstruktor aufgerufen
    public void init() {
        System.out.println("2️⃣ @PostConstruct - Service ist bereit!");
        
        // Testdaten generieren
        Faker faker = new Faker(new Locale("de"));
        for (long i = 1; i <= 100; i++) {
            persons.add(new Person(
                i,
                faker.name().firstName(),
                faker.name().lastName(),
                faker.internet().emailAddress()
            ));
        }
        
        System.out.println("3️⃣ " + persons.size() + " Personen generiert");
    }
    
    @PreDestroy  // Wird BEIM Shutdown aufgerufen
    public void cleanup() {
        System.out.println("🔴 @PreDestroy - PersonService wird aufgeräumt!");
    }
    
    public List<Person> getAllPersons() {
        return persons;
    }
    
    public Person getPersonById(Long id) {
        return persons.stream()
                .filter(p -> p.getId().equals(id))
                .findFirst()
                .orElse(null);
    }
    
    public Person createPerson(Person person) {
        person.setId((long) (persons.size() + 1));
        persons.add(person);
        return person;
    }
}

Beim Start siehst du:

1️⃣ PersonService Konstruktor wird aufgerufen
2️⃣ @PostConstruct - Service ist bereit!
3️⃣ 100 Personen generiert
...
Tomcat started on port(s): 8080 (http)

Beim Shutdown (Ctrl+C):

🔴 @PreDestroy - PersonService wird aufgeräumt!

Bean Lifecycle:

  1. Konstruktor wird aufgerufen
  2. Dependencies werden injected
  3. @PostConstruct Methoden ausführen
  4. Bean ist bereit für Nutzung
  5. …App läuft…
  6. @PreDestroy Methoden ausführen
  7. Bean wird zerstört

🔵 BONUS: TIEFES SPRING-VERSTÄNDNIS

Schritt 8: Mehrere Service-Implementierungen

Was wenn du zwei verschiedene PersonServices hast?

8.1 Interface erstellen

Erstelle: src/main/java/com/example/helloworldapi/service/PersonServiceInterface.java

package com.example.helloworldapi.service;

import com.example.helloworldapi.model.Person;
import java.util.List;

public interface PersonServiceInterface {
    List<Person> getAllPersons();
    Person getPersonById(Long id);
    Person createPerson(Person person);
}

8.2 Zwei Implementierungen

Benenne PersonService um zu DatabasePersonService:

@Service("databaseService")
public class DatabasePersonService implements PersonServiceInterface {
    // ... wie vorher ...
}

Erstelle zweiten Service:

package com.example.helloworldapi.service;

import com.example.helloworldapi.model.Person;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service("mockService")
public class MockPersonService implements PersonServiceInterface {
    
    @Override
    public List<Person> getAllPersons() {
        List<Person> persons = new ArrayList<>();
        persons.add(new Person(1L, "Test", "User", "test@example.com"));
        return persons;
    }
    
    @Override
    public Person getPersonById(Long id) {
        return new Person(id, "Test", "User", "test@example.com");
    }
    
    @Override
    public Person createPerson(Person person) {
        return person;
    }
}

8.3 Controller mit @Qualifier

Problem: Welchen Service soll Spring injizieren?

@RestController
@RequestMapping("/api/persons")
@RequiredArgsConstructor
public class PersonController {
    
    @Qualifier("databaseService")  // Oder "mockService"
    private final PersonServiceInterface personService;
    
    // ... methods ...
}

Alternative mit @Primary:

@Service
@Primary  // Dieser wird standardmäßig genommen
public class DatabasePersonService implements PersonServiceInterface {
    // ...
}

Bonus-Aufgaben

Weitere Ideen zum Üben:

  1. Eigenen Service erstellen:
    • StatisticsService der Statistiken über Personen berechnet
    • Anzahl Personen, häufigste Namen, etc.
  2. Service in Service:
    • PersonService nutzt ValidationService
    • Validierung von Email-Adressen
  3. ApplicationContext API erforschen:
    • context.getBean() nutzen
    • Beans nach Typ suchen
    • Bean-Details auslesen
  4. Eigenen @Bean erstellen: @Configuration public class AppConfig { @Bean public Faker faker() { return new Faker(new Locale("de")); } }

Probier dich aus! Das ist deine Spielwiese. 🎮

Offizielle Dokumentation von Spring Framework zum Thema DI: „Dependency Injection :: Spring Framework” – erklärt das Prinzip von DI, inkl. Konstruktor- und Setter‐Injection. Home

Artikel bei Baeldung: „Spring Dependency Injection Series” – führt Schritt für Schritt in DI in Spring ein, inkl. Annotationen, Komponenten­scanning, Entkopplung. Baeldung on Kotlin

Tutorial bei GeeksforGeeks: „Spring Dependency Injection with Example” – praxisorientiert mit Codebeispielen zu Konstruktor‐ und Setter‐Injection. GeeksforGeeks

Artikel bei Baeldung: „Inversion of Control and Dependency Injection in Spring” – behandelt die Grundidee von IoC & DI und wie Spring diese umsetzt. Baeldung on Kotlin

Tutorial bei DigitalOcean: „Spring Dependency Injection” – mit Beispielen zu annotierter und XML‐Konfiguration, Testbarkeit, Bean‐Scopes. digitalocean.com


✅ Checkpoint: Hast du Tag 2 geschafft?

Grundlagen (🟢):

  • [ ] PersonService erstellt (@Service)
  • [ ] PersonController nutzt PersonService (Constructor Injection)
  • [ ] final beim Service im Controller
  • [ ] GET /api/persons funktioniert weiterhin
  • [ ] Du verstehst den Unterschied zwischen Field/Setter/Constructor Injection

Professional (🟡):

  • [ ] Lombok Dependency hinzugefügt
  • [ ] @RequiredArgsConstructor im Controller verwendet
  • [ ] ContextController erstellt
  • [ ] /api/context/my-beans zeigt deine Beans
  • [ ] Bean Lifecycle mit @PostConstruct/@PreDestroy verstanden

Bonus (🔵):

  • [ ] Interface für PersonService erstellt
  • [ ] Mehrere Service-Implementierungen ausprobiert
  • [ ] @Qualifier oder @Primary verwendet

Alles ✅? Du bist bereit für Tag 3!

Nicht alles funktioniert?

  • PersonService nicht gefunden? Prüfe ob @Service Annotation da ist
  • Injection funktioniert nicht? Prüfe ob Service im gleichen Package oder Sub-Package
  • Lombok Error? Clean and Build ausführen
  • Mehrere Beans Error? @Primary oder @Qualifier verwenden

🔥 Elyndras Real Talk:

Beim Frühstück heute wurde es lebhaft. Marcus hatte eine Frage, und plötzlich war die ganze Runde dabei.

Marcus: „Warum macht Spring das alles automatisch? Früher haben wir doch Objekte selbst erstellt!“

Franz-Martin (unser Legacy-Experte) lehnte sich zurück: „Ah, die guten alten Zeiten! 2005, Java EE 5, da haben wir noch…“

Bernd unterbrach ihn grinsend: „Franz-Martin, bitte nicht die ‚früher war alles besser‘ Geschichte!“

Aber Franz-Martin hatte einen Punkt. Früher sah Code wirklich so aus:

// 2005 - Java EE ohne Spring - pure Legacy!
public class PersonController {
    private PersonService service;
    
    public PersonController() {
        // Alles manuell instanziieren!
        PersonRepository repo = new PersonRepository();
        DatabaseConnection conn = new DatabaseConnection("jdbc:mysql://...");
        repo.setConnection(conn);
        this.service = new PersonService(repo);
    }
}

Franz-Martin: „Und das war in JEDEM Controller! Stellt euch vor – 50 Controller, alle mit dem gleichen Boilerplate!“

Nova (unsere Junior-Entwicklerin): „Das klingt furchtbar! Und wenn sich was ändert?“

Franz-Martin: „Genau! Wenn die DatabaseConnection einen neuen Parameter brauchte, musstest du ALLE 50 Controller anfassen!“

Bernd (lacht): „Und dann kam Spring und hat gesagt: ‚Kinder, lasst mal Papa das machen!'“

Marcus: „Aber ist das nicht… kontrollverlust? Ich erstelle doch gerne meine eigenen Objekte!“

Ich (Elyndra): „Das dachte ich am Anfang auch. Aber schau dir den Unterschied an:“

Ohne Spring (Legacy):

public class PersonController {
    private PersonService service;
    
    public PersonController() {
        PersonRepository repo = new PersonRepository();
        this.service = new PersonService(repo);
    }
}

public class OrderController {
    private PersonService service;
    
    public OrderController() {
        PersonRepository repo = new PersonRepository();
        this.service = new PersonService(repo);  // NEUE Instanz!
    }
}

Probleme:

  • ❌ Jeder Controller erstellt eigene Service-Instanz (ineffizient!)
  • ❌ Jeder Service erstellt eigene Repository-Instanz
  • ❌ 50 Controller = 50 Service-Instanzen im RAM
  • ❌ Schwer zu testen (wie mocke ich Abhängigkeiten?)
  • ❌ Änderungen im Service = Änderungen in ALLEN Controllern
  • ❌ Kein zentrales Management
  • ❌ Circular Dependencies? Viel Spaß beim Debuggen!

Mit Spring:

@RestController
@RequiredArgsConstructor
public class PersonController {
    private final PersonService service;  // Spring kümmert sich drum!
}

@RestController
@RequiredArgsConstructor
public class OrderController {
    private final PersonService service;  // GLEICHE Instanz wie PersonController!
}

Vorteile:

  • ✅ Spring erstellt eine Service-Instanz (Singleton)
  • ✅ Alle Controller teilen sich diese Instanz (effizient!)
  • ✅ Einfach zu testen (Service-Mock injizieren)
  • ✅ Änderungen zentral (nur im Service)
  • ✅ Spring managed den Lifecycle (erstellen, aufräumen)
  • ✅ Spring löst Dependencies automatisch

Franz-Martin: „Wir hatten damals auch das Problem mit Circular Dependencies. PersonService braucht OrderService, OrderService braucht PersonService – endlose NullPointerExceptions!“

Bernd: „Und heute?“

Franz-Martin (lächelt): „Spring merkt das beim Start und wirft einen klaren Fehler: ‚Hey, du hast einen Zyklus!‘ Das haben wir früher oft erst in Production gemerkt.“

Nova: „Also ist Inversion of Control wie… die Klasse gibt Verantwortung ab?“

Ich (Elyndra): „Genau! Das nennt man Inversion of Control (IoC):
Nicht du erstellst Objekte – Spring macht das für dich!“

Marcus (zu Eomma): „Das ist wie ein guter Werkzeugkoffer, Eomma. Du brauchst ihn nicht selbst zu bauen – du nutzt ihn einfach und konzentrierst dich aufs Häuser bauen!“

Bernd (lacht): „Perfekte Analogie! Spring ist der Mixer für Java-Objekte!“

Franz-Martin: „Und wer XML-Konfiguration aus Java EE 5 kennt, weiß Spring Boot zu schätzen. Früher hatten wir 500-Zeilen XML-Dateien nur um drei Beans zu konfigurieren!“

Nova: „500 Zeilen XML? Ernsthaft?“

Franz-Martin: „Oh ja. Und wenn ein Tippfehler drin war, hat die App erst beim Start gemerkt – nach 2 Minuten Bootzeit!“

Ich (Elyndra): „Heute macht Spring Boot 99% automatisch. Das ist Evolution.“

Marcus: „Und wie würdet ihr das Franz-Martin erklären, wenn er 2005 geblieben wäre?“

Bernd: „Franz-Martin würde sagen: ‚Alte Werkzeuge haben ihren Platz in der Geschichte, aber neue machen die Arbeit leichter!'“

Franz-Martin (lacht): „Genau das würde ich sagen! Und ich sage es auch heute noch. Legacy hat uns viel gelehrt – aber ich gehe nicht zurück!“

Das ist Spring Boot. Container Management, Dependency Injection, Auto-Configuration – alles automatisch, damit du dich aufs Wichtige konzentrieren kannst: deine Business-Logik!

Marcus‘ Fazit: „Also kontrollverlust ist eigentlich kontrolle durch besseres Management?“

Ich (Elyndra): „Exakt. Du gibst die Kontrolle über das ‚Wie‘ ab (Objekt-Erstellung) und behältst die Kontrolle über das ‚Was‘ (deine Business-Logik).“

Bernd: „Und genau deshalb liebe ich Spring seit 15 Jahren!“

Franz-Martin: „Seit 20 Jahren – ich war beim ersten Release dabei!“ (alle lachen)

Nova: „Eine Frage noch – wenn Spring alles managed, wie debugge ich dann, was Spring eigentlich erstellt hat?“

Ich (Elyndra): „Gute Frage! Deshalb haben wir heute den ContextController gebaut. Damit kannst du sehen, was Spring alles gemacht hat:“

curl http://localhost:8080/api/context/my-beans
# ["contextController", "helloController", "personController", "personService"]

curl http://localhost:8080/api/context/bean-count
# 137

Nova (Augen weit): „137 Beans? Spring hat 137 Objekte erstellt?“

Bernd: „Willkommen in der Spring Boot Auto-Configuration! Tomcat, Jackson, alle Request-Handler, Error-Handler, alles automatisch!“

Franz-Martin: „Früher mussten wir jedes dieser 137 Objekte manuell in XML konfigurieren!“

Marcus: „Und wenn ich mehr Kontrolle will?“

Ich (Elyndra): „Dann überschreibst du die Auto-Configuration. Spring sagt: ‚Ich mache das für dich, aber wenn du es anders willst, bitte sehr!‘ Mit @Primary oder @Qualifier kannst du genau steuern, welche Bean verwendet wird.“

Bernd: „Eomma, du verstehst Software besser als die meisten Entwickler!“ (alle lachen)

Das ist die Macht von Spring Boot:

  • Du bekommst 137 vorkonfigurierte Beans geschenkt
  • Du schreibst nur noch deine Business-Logik
  • Und wenn du was anders willst? Kein Problem, du überschreibst es einfach

Franz-Martin: „Wer Legacy Java EE kennt, versteht warum ich Spring liebe. Rod Johnson hat 2004 die Java-Welt verändert.“

Marcus: „Rod Johnson?“

Franz-Martin: „Der Erfinder von Spring Framework. Sein Buch ‚Expert One-on-One J2EE Design and Development‘ war revolutionär. Er sagte: ‚Java EE ist zu kompliziert‘ und baute Spring.“

Bernd: „Und 20 Jahre später bauen wir immer noch damit!“

Ich (Elyndra): „Weil gute Konzepte zeitlos sind. Dependency Injection, IoC, das sind keine Buzzwords – das sind bewährte Patterns.“

Nova: „Und für mich als Junior ist das perfekt – ich konzentriere mich auf Logik, nicht auf Objekt-Management!“

Marcus nickt: „Okay, ich bin überzeugt. Spring ist nicht Kontrollverlust, sondern Delegation an einen Experten.“

Franz-Martin: „Wie bei einem guten Werkzeugkoffer!“

Eomma: „Wie bei meinem Mixer!“

Alle lachen.

Genau das ist Spring Boot. 🚀


❓ FAQ (Häufige Fragen)

Q: Was ist der Unterschied zwischen @Component und @Service?
A: Technisch: Fast keiner. Semantisch: @Service zeigt „das ist Business-Logik“, @Component ist generisch. Nutze @Service für Klarheit!

Q: Kann ich mehrere Instanzen einer Bean haben?
A: Ja! Mit @Scope("prototype"). Default ist aber Singleton (eine Instanz für alle).

Q: Warum ist Field Injection schlecht?
A: 1) Nicht testbar ohne Spring, 2) Kann nicht final sein, 3) Versteckte Dependencies. Constructor Injection ist immer besser!

Q: Brauche ich @Autowired beim Konstruktor?
A: Nein! Seit Spring 4.3 ist @Autowired optional, wenn die Klasse nur einen Konstruktor hat.

Q: Was wenn ich zwei PersonServices habe?
A: Spring wirft einen Fehler! Lösung: @Primary auf einem Service oder @Qualifier("serviceName") beim Injizieren.

Q: Kann ich ApplicationContext direkt injizieren?
A: Ja! Siehe ContextController – das ist nützlich für Debugging.

Q: Marcus meinte „früher war das anders“ – wie war das?
A: Früher (Java EE, 2000-2010) musstest du alles in XML konfigurieren. Spring Boot macht 99% automatisch. Das ist Evolution, und wie Marcus sagen würde: „Alte Werkzeuge haben ihren Platz in der Geschichte, aber neue machen die Arbeit leichter.“


📅 Nächster Kurstag: Tag 3

Morgen im Kurs / Nächster Blogbeitrag:

„Thymeleaf & HTML – Webseiten statt JSON“

Was du lernen wirst:

  • Unterschied @RestController vs. @Controller
  • Thymeleaf Template Engine Setup
  • Model & View Konzept verstehen
  • Erste HTML-Seite mit dynamischen Daten
  • th:text, th:each, th:href nutzen
  • PersonService in HTML-Views verwenden

Warum wichtig? Morgen wechselst du von JSON zu echten Webseiten! Du baust dieselbe Person-API, aber mit HTML statt JSON-Response. Dein PersonService wird wiederverwendet – das ist die Macht der Service-Architektur!

Voraussetzung: Tag 2 abgeschlossen (Service-Layer funktioniert)

👉 Zum Blogbeitrag Tag 3 (erscheint morgen)


📍 Deine Position im Kurs

TagThemaStatus
✅ 1Erste REST APIAbgeschlossen
✅ 2Spring Container & DI + CRUD👉 DU BIST HIER!
3@Controller & Thymeleaf BasicsNoch nicht freigeschaltet
4Thymeleaf Forms & MVC-PatternNoch nicht freigeschaltet
5Konfiguration & LoggingNoch nicht freigeschaltet
6DI & AOP im DetailNoch nicht freigeschaltet
7Scopes in SpringNoch nicht freigeschaltet
8WebSocketsNoch nicht freigeschaltet
9JAX-RS in Spring BootNoch nicht freigeschaltet
10Integration & AbschlussNoch nicht freigeschaltet

Du hast 20% des Kurses geschafft! 💪

Alle Blogbeiträge dieser Serie:
👉 Spring Boot Basic – Komplette Übersicht


📥 Download & Ressourcen

Projekt zum Download:
👉 tag2-spring-boot-basic-v1.0.zip (Stand: 23.10.2025)

Was ist im ZIP enthalten:

  • ✅ Komplettes Maven-Projekt von Tag 1+2
  • ✅ PersonService implementiert
  • ✅ Constructor Injection im Controller
  • ✅ ContextController zum Debuggen
  • ✅ Lombok @RequiredArgsConstructor Beispiele
  • ✅ Bean Lifecycle Beispiele
  • ✅ Umfangreiche README mit allen Dokumentationen
  • ✅ Code-Kommentare auf Deutsch

Projekt starten:

# ZIP entpacken
# In NetBeans öffnen: File → Open Project
# Oder im Terminal:
cd springcontainerdi
mvn spring-boot:run

# Testen:
curl http://localhost:8080/api/persons
curl http://localhost:8080/api/context/my-beans

Probleme? Issue melden auf GitHub oder schreib mir: elyndra@java-developer.online


Das war Tag 2 von Spring Boot Basic!

Du kannst jetzt:

  • ✅ Spring Container verstehen und erklären
  • ✅ @Component, @Service, @Controller richtig einsetzen
  • ✅ Dependency Injection (alle drei Varianten)
  • ✅ Constructor Injection als Best Practice nutzen
  • ✅ Lombok @RequiredArgsConstructor verwenden
  • ✅ Spring Container debuggen (Beans auslesen)
  • ✅ Bean Lifecycle mit @PostConstruct/@PreDestroy

Morgen lernst du Thymeleaf für HTML-Views! 🚀

Keep coding, keep learning!
🖖 Live long and prosper

📧 Email: elyndra.valen@java-developer.online

🌐 Website: https://www.java-developer.online

💻 GitHub: https://github.com/ElyndraValen


Tag 3 erscheint morgen. Bis dahin: Happy Coding!


Tags: #SpringBoot #DependencyInjection #Container #Java #Tag2 #Lombok #Service #IoC

Autor

  • Elyndra Valen

    28 Jahre alt, wurde kürzlich zur Senior Entwicklerin befördert nach 4 Jahren intensiver Java-Entwicklung. Elyndra kennt die wichtigsten Frameworks und Patterns, beginnt aber gerade erst, die tieferen Zusammenhänge und Architektur-Entscheidungen zu verstehen. Sie ist die Brücke zwischen Junior- und Senior-Welt im Team.