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


Scopes in Spring

📍 Deine Position im Kurs

TagThemaStatus
✅ 1Erste REST APIAbgeschlossen
✅ 2Spring Container & DIAbgeschlossen
✅ 3@Controller & Thymeleaf BasicsAbgeschlossen
✅ 4Thymeleaf Forms & MVC-PatternAbgeschlossen
✅ 5Konfiguration & LoggingAbgeschlossen
✅ 6DI & AOP im DetailAbgeschlossen
→ 7Scopes in Spring👉 DU BIST HIER!
8WebSocketsNoch nicht freigeschaltet
9JAX-RS in Spring BootNoch nicht freigeschaltet
10Integration & AbschlussNoch nicht freigeschaltet

Modul: Spring Boot Basic (10 Arbeitstage)
Dein Ziel: Bean Scopes verstehen & richtig einsetzen!


📋 Voraussetzungen

Du brauchst:

  • ✅ Tag 1-6 abgeschlossen
  • ✅ Grundverständnis von Beans und Dependency Injection
  • ✅ @Component, @Service, @Controller funktionieren
  • ✅ PersonService und PersonViewController sind implementiert
  • 💡 Bonus: Java Web Basic Kurs (Pizza Service mit Session Scope) kennst du schon!

Tag 6 verpasst? → Hier geht’s zum Blogbeitrag Tag 6


⚡ Was dich heute erwartet

Bisher: Du hast @Service@Controller@Component benutzt – aber was bedeuten sie wirklich?

Heute: Du verstehst die semantische Bedeutung der Annotations UND wie Spring deine Beans verwaltet!

Das Problem:

  • Was ist der Unterschied zwischen @Service, @Controller, @Component?
  • Welche Scopes machen bei welcher Annotation Sinn?
  • Wann erstellt Spring neue Instanzen?
  • Was ist der Unterschied zwischen Singleton und Prototype?
  • Wie funktionieren Request und Session Scopes?

Die Lösung:

  • Stereotype Annotations semantisch verstehen
  • Architektur-Regeln für Scopes lernen
  • Singleton, Prototype, Request, Session Scopes beherrschen
  • @Scope richtig einsetzen
  • Migration von JSP/Servlets zu Spring Boot verstehen
  • Best Practices für Production-Apps

🎯 Dein Lernpfad heute

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

🟢 Grundlagen (Schritte 1-3)

Was du lernst:

  • Stereotype Annotations semantisch verstehen (@Service, @Controller, @Component)
  • Architektur-Regeln: Welche Annotation = welcher Scope?
  • Bean Scopes Konzept verstehen
  • Singleton Scope – der Standard (DEFAULT)
  • Prototype Scope – jedes Mal neu

Ziel: Du verstehst die Semantik der Annotations und die wichtigsten Scopes


🟡 Professional (Schritte 4-5)

Was du lernst:

  • Request Scope für HTTP-Request-Daten
  • Session Scope für User-spezifische Daten
  • Die Brücke von JSP/Servlets zu Spring Boot
  • PersonFavorites mit Session Scope implementieren
  • Scoped Proxies verstehen und nutzen

Ziel: Production-Ready Session und Request Scope implementiert


🔵 Bonus: Enterprise Features (Schritte 6-7)

Was du lernst:

  • Application Scope für globale Statistiken
  • Scope-Mix-Fehler vermeiden (@Service im Session Scope!)
  • Thread-Safety Regeln für Scopes
  • Migration-Patterns: Legacy → Modern
  • Best Practices für Production-Apps

Ziel: Enterprise-Level Scope-Management beherrschen


💻 Los geht’s!

🟢 GRUNDLAGEN (Schritte 1-3)

Schritt 1: Stereotype Annotations – Semantik & Architektur (1 Stunde)

Bevor wir über Scopes sprechen: Du nutzt @Service@Controller@Component seit Tag 2, aber was bedeuten sie wirklich? Und welche Scopes machen bei welcher Annotation Sinn?

1.1 Die 5 Stereotype Annotations

Spring bietet 5 spezielle Annotations für Komponenten:

@Component      // Generische Spring-Komponente
@Service        // Business-Logic Layer
@Controller     // Presentation Layer (MVC)
@RestController // REST API Layer
@Repository     // Data-Access Layer

Die Frage: Was ist der Unterschied? Sind das nur verschiedene Namen für dasselbe?

Die Antwort: NEIN! Sie haben semantische Bedeutung und architektonische Implikationen!

1.2 @Component – Die Basis

@Component
public class EmailValidator {
    public boolean isValid(String email) {
        return email.contains("@");
    }
}

Was ist @Component?

  • Die generische Spring-Komponente
  • Basis für alle anderen Stereotype (@Service, @Controller, etc. erweitern @Component)
  • Nutzen wenn: Keine spezifische Layer-Zugehörigkeit

Typische Use Cases:

  • Utility-Klassen (EmailValidator, DateFormatter)
  • Helper-Klassen (FileUploader, ImageResizer)
  • State-Holder (UserCart, UserPreferences, RequestContext)

Scope-Empfehlung:

  • ✅ Singleton (für Utilities/Helpers)
  • ✅ Session (für User-State wie UserCart)
  • ✅ Request (für Request-State wie RequestContext)
  • ✅ Prototype (für Builder-Pattern)

@Component ist FLEXIBEL – Scope hängt vom Zweck ab!

1.3 @Service – Business Logic Layer

@Service
public class PersonService {
    
    public Person createPerson(Person person) {
        // Business-Logik: Validierung, Berechnung, Orchestrierung
        validatePerson(person);
        einrichtenPersonData(person);
        return person;
    }
    
    private void validatePerson(Person person) {
        // Business-Regeln prüfen
    }
}

Was ist @Service?

  • Markiert Business-Logic Layer
  • Enthält Geschäftslogik, keine technischen Details
  • Orchestriert zwischen Controller und Repository

Typische Aufgaben:

  • Business-Regeln umsetzen (Validierung, Berechnung)
  • Transaktionen koordinieren
  • Verschiedene Repositories orchestrieren
  • Domain-Logik kapseln

Scope-Empfehlung:

  • ✅ Singleton (IMMER!)
  • ❌ Session Scope = FALSCH!
  • ❌ Request Scope = FALSCH!
  • ❌ Prototype = FALSCH!

Warum immer Singleton?

// Business-Logik sollte ZUSTANDSLOS sein!

// ✅ RICHTIG - Keine Instanz-Variablen für Request/User-Daten
@Service
public class OrderService {
    
    public void processOrder(Order order, User user) {
        // Alle Daten als Parameter, kein Instanz-State!
        validateOrder(order);
        calculatePrice(order);
        applyUserDiscount(order, user);
    }
}

// ❌ FALSCH - Instanz-Variable für User-Daten
@Service
@Scope("session")  // ← ANTI-PATTERN!
public class OrderService {
    
    private User currentUser;  // ← FALSCH!
    
    public void setCurrentUser(User user) {
        this.currentUser = user;
    }
    
    public void processOrder(Order order) {
        // Service kennt "seinen" User - FALSCHE Architektur!
        applyUserDiscount(order, currentUser);
    }
}

Die Regel: @Service = IMMER Singleton, weil Business-Logik zustandslos sein muss!

1.4 @Controller – Presentation Layer (MVC)

@Controller
public class PersonViewController {
    
    @Autowired
    private PersonService personService;  // Service injiziert
    
    @GetMapping("/persons")
    public String listPersons(Model model) {
        // Controller-Aufgabe: Request verarbeiten, Service aufrufen, View zurückgeben
        List<Person> persons = personService.getAllPersons();
        model.addAttribute("persons", persons);
        return "persons-list";  // Thymeleaf Template
    }
}

Was ist @Controller?

  • Markiert Presentation Layer (MVC-Pattern)
  • Verarbeitet HTTP-Requests
  • Gibt Views zurück (HTML via Thymeleaf, JSP, etc.)
  • Koordiniert zwischen HTTP und Service

Scope-Empfehlung:

  • ✅ Singleton (IMMER!)
  • ❌ Session Scope = FALSCH!
  • ❌ Request Scope = FALSCH!

Die Regel: @Controller = IMMER Singleton, weil MVC-Controller zustandslos sein sollen!

1.5 @RestController – REST API Layer

@RestController
@RequestMapping("/api/persons")
public class PersonApiController {
    
    @Autowired
    private PersonService personService;
    
    @GetMapping
    public List<Person> getAllPersons() {
        // REST-Controller-Aufgabe: JSON zurückgeben
        return personService.getAllPersons();
    }
    
    @PostMapping
    public ResponseEntity<Person> createPerson(@RequestBody Person person) {
        Person created = personService.createPerson(person);
        return ResponseEntity.status(HttpStatus.CREATED).body(created);
    }
}

Was ist @RestController?

  • Kombination aus @Controller + @ResponseBody
  • Markiert REST API Layer
  • Gibt JSON/XML zurück (keine Views!)
  • Stateless HTTP (REST-Prinzip!)

Scope-Empfehlung:

  • ✅ Singleton (IMMER!)
  • ❌ Session Scope = FALSCH! (REST sollte stateless sein)
  • ❌ Request Scope = FALSCH!

Die Regel: @RestController = IMMER Singleton, weil REST stateless HTTP sein muss!

1.6 @Repository – Die Annotation die du (noch) nicht brauchst!

Du hast @Repository schon gesehen – in Spring-Tutorials, ChatGPT-Antworten, Stack Overflow. Aber warum nutzen wir es im Basic-Kurs nicht?

Was ist @Repository?

  • Markiert Data-Access Layer (Datenbank-Zugriff)
  • Arbeitet mit JPA/Hibernate, SQL, Transactions
  • Kapselt CRUD-Operationen gegen eine Datenbank

Warum NICHT im Basic-Kurs?

Die ehrliche Antwort: Weil es zu früh wäre!

Datenbank-Layer ist KOMPLEX:
├─ JPA/Hibernate (ORM verstehen)
├─ @Entity, @Table, @Column (Mapping)
├─ @Transactional (Transaction-Management)
├─ Relationships (@OneToMany, @ManyToOne)
├─ Lazy vs Eager Loading
├─ N+1 Problem verstehen
└─ Connection Pooling, Performance

Das sind 2-3 EIGENE Kurstage!

Unsere Strategie: Schicht für Schicht!

┌─────────────────────────────────┐
│   Spring Boot BASIC             │
│                                 │
│   @Controller (Presentation)    │  ← Tag 3-4
│   @Service (Business Logic)     │  ← Tag 2-6
│   @Component (Utilities/State)  │  ← Tag 2-7
│                                 │
│   ArrayList (In-Memory!)        │  ← Reicht für Basic!
└─────────────────────────────────┘

┌─────────────────────────────────┐
│   Spring Boot AUFBAU            │
│                                 │
│   @Repository (Data Access)     │  ← Kommt später!
│   JPA/Hibernate                 │
│   Database (PostgreSQL/MySQL)   │
└─────────────────────────────────┘

Merksatz:

„@Repository ist wie @Service – IMMER Singleton! Aber die Database-Komplexität kommt erst im Aufbau-Kurs, wenn die Grundlagen sitzen!“

1.7 Zusammenfassung: Architektur-Regeln (Basic-Kurs)

Die Tabelle (für Basic-Kurs):

AnnotationLayerAufgabeScopeIm Basic-Kurs?
@ComponentKeine spezifischeUtilities, State-HolderFLEXIBEL✅ JA
@ServiceBusiness LogicGeschäftslogikSingleton✅ JA
@ControllerPresentation (MVC)Request → ViewSingleton✅ JA
@RestControllerREST APIRequest → JSONSingleton✅ JA
@RepositoryData AccessCRUD, DB-ZugriffSingleton⏰ AUFBAU-KURS

Die goldene Regel (Basic-Kurs):

@Service, @Controller, @RestController
    ↓
Architektonische LAYER
    ↓
IMMER Singleton (zustandslos!)
    ↓
Nur @Component darf flexibel sein!

@Repository kennst du jetzt, kommt in Spring Boot Aufbau!

Schritt 2: Was sind Bean Scopes?

2.1 Das Konzept verstehen

Stell dir vor: Du hast einen PersonService.

@Service
public class PersonService {
    private final List<Person> persons = new ArrayList<>();
    
    public void addPerson(Person person) {
        persons.add(person);
    }
}

Die Frage: Wann und wie oft erstellt Spring diese Bean?

User A ruft /persons auf
User B ruft /persons auf
User C ruft /persons auf

Frage: Nutzen alle 3 User DIESELBE PersonService-Instanz?
Oder bekommt jeder User seine EIGENE Instanz?

Die Antwort: Das bestimmt der Bean Scope!

2.2 Die 5 wichtigsten Scopes

ScopeWann erstellt?LebensdauerVerwendung
singletonEINMAL beim StartBis App-EndeDEFAULT! Services, Repositories
prototypeJEDES MAL bei @AutowiredBis Garbage CollectionTemporäre Objekte
requestPRO HTTP-RequestBis Request fertigRequest-spezifische Daten
sessionPRO HTTP-SessionBis Session-TimeoutUser-spezifische Daten
applicationEINMAL pro ServletContextBis App-EndeGlobale App-Daten

2.3 Singleton – Der Standard (DEFAULT!)

@Service  // Singleton ist DEFAULT!
public class PersonService {
    private final List<Person> persons = new ArrayList<>();
    
    public void addPerson(Person person) {
        persons.add(person);
    }
    
    public List<Person> getAllPersons() {
        return persons;
    }
}

Was passiert:

App startet
    ↓
Spring erstellt EINE PersonService-Instanz
    ↓
User A nutzt diese Instanz
User B nutzt DIESELBE Instanz
User C nutzt DIESELBE Instanz
    ↓
App stoppt → Bean wird zerstört

Visualisierung:

┌─────────────────────────┐
│   Spring Container      │
│                         │
│  ┌──────────────────┐  │
│  │ PersonService    │  │
│  │ (Singleton)      │  │
│  │ - persons: [...]  │  │
│  └──────────────────┘  │
│         ↑ ↑ ↑          │
└─────────│─│─│──────────┘
          │ │ │
      UserA UserB UserC
      (alle nutzen dieselbe Instanz!)

Vorteile:

  • ✅ Performance – Nur einmal erstellen
  • ✅ Memory-Effizienz – Eine Instanz für alle
  • ✅ Zustandsfreie Services perfekt

Nachteile:

  • ⚠️ Shared State – Alle User teilen Daten
  • ⚠️ Thread-Safety beachten!

2.4 Wann ist Singleton problematisch?

Problem-Szenario:

@Service  // Singleton!
public class PersonService {
    
    // GEFAHR: Shared State!
    private String currentUserName;  // Alle User teilen diese Variable!
    
    public void setCurrentUser(String userName) {
        this.currentUserName = userName;
    }
    
    public List<Person> getPersonsForCurrentUser() {
        // User A setzt "Alice"
        // User B setzt "Bob" → überschreibt "Alice"
        // User A liest... und bekommt "Bob"! 😱
        log.info("Getting persons for: {}", currentUserName);
        return persons;
    }
}

Was schief geht:

Zeit: 10:00:00.001 - User A ruft setCurrentUser("Alice")
                     → currentUserName = "Alice"
Zeit: 10:00:00.002 - User B ruft setCurrentUser("Bob")
                     → currentUserName = "Bob" (überschreibt Alice!)
Zeit: 10:00:00.003 - User A ruft getPersonsForCurrentUser()
                     → Log zeigt "Bob" statt "Alice"! 😱

Die Regel: Singleton Beans dürfen KEINEN Request-spezifischen State haben!


Schritt 3: Prototype Scope – Jedes Mal neu

3.1 Prototype verstehen

@Service
@Scope("prototype")  // Jedes Mal NEU!
public class ReportGenerator {
    
    private String reportTitle;
    private List<String> data = new ArrayList<>();
    
    public void setTitle(String title) {
        this.reportTitle = title;
    }
    
    public void addData(String item) {
        data.add(item);
    }
    
    public String generateReport() {
        return reportTitle + ": " + String.join(", ", data);
    }
}

Was passiert:

@Controller
@RequiredArgsConstructor
public class ReportController {
    
    private final ReportGenerator reportGenerator;  // Neu bei JEDEM Inject!
    
    @GetMapping("/report1")
    public String report1() {
        // reportGenerator ist NEUE Instanz
        reportGenerator.setTitle("Report 1");
        reportGenerator.addData("Data A");
        return reportGenerator.generateReport();
    }
}

Aber ACHTUNG! Das funktioniert NICHT wie erwartet!

Problem: Der Controller selbst ist Singleton, also bekommt er nur EINMAL einen ReportGenerator injiziert!

3.2 Prototype richtig nutzen

Lösung 1: ObjectFactory

@Controller
@RequiredArgsConstructor
public class ReportController {
    
    private final ObjectFactory<ReportGenerator> reportGeneratorFactory;
    
    @GetMapping("/report1")
    public String report1() {
        ReportGenerator generator = reportGeneratorFactory.getObject();  // NEU!
        generator.setTitle("Report 1");
        generator.addData("Data A");
        return generator.generateReport();
    }
}

Lösung 2: @Lookup (Eleganteste Lösung!)

@Component
public abstract class ReportService {
    
    public String createReport(String title, List<String> data) {
        ReportGenerator generator = getReportGenerator();  // Spring füllt diese Methode!
        generator.setTitle(title);
        data.forEach(generator::addData);
        return generator.generateReport();
    }
    
    @Lookup  // Spring implementiert diese Methode automatisch!
    protected abstract ReportGenerator getReportGenerator();
}

Wann Prototype nutzen?

SzenarioSingleton oder Prototype?
Zustandsloser ServiceSingleton
RepositorySingleton
ControllerSingleton
Temporäre Berechnungen mit StatePrototype
Builder-PatternPrototype
Command-PatternPrototype

✅ Checkpoint Grundlagen

Kontrolliere:

  • [ ] Du verstehst die Semantik von @Service, @Controller, @Component
  • [ ] Du kennst die Architektur-Regeln (welche Annotation = welcher Scope)
  • [ ] Du weißt warum @Repository im Basic-Kurs noch nicht dran ist
  • [ ] Du verstehst das Bean Scope Konzept
  • [ ] Du kennst Singleton als DEFAULT Scope
  • [ ] Du verstehst wann Singleton problematisch ist
  • [ ] Du weißt wie Prototype funktioniert
  • [ ] Du kannst Prototype mit ObjectFactory nutzen

Alles ✅? Weiter zu 🟡 Professional!


🟡 PROFESSIONAL (Schritte 4-5)

Schritt 4: Request Scope – Pro HTTP-Request (1 Stunde)

4.1 Request Scope verstehen

Szenario: Du willst Request-spezifische Informationen tracken.

@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
    
    private String requestId;
    private String userName;
    private LocalDateTime requestTime;
    
    @PostConstruct
    public void init() {
        this.requestId = UUID.randomUUID().toString();
        this.requestTime = LocalDateTime.now();
        log.info("Created RequestContext: {}", requestId);
    }
    
    @PreDestroy
    public void cleanup() {
        log.info("Destroying RequestContext: {}", requestId);
    }
    
    // Getters/Setters
}

Was macht proxyMode = ScopedProxyMode.TARGET_CLASS?

Problem:
PersonService ist Singleton (lebt bis App-Ende)
RequestContext ist Request-Scoped (neu bei jedem Request)

→ Wie injiziert man Request-Bean in Singleton-Bean?

Lösung: Scoped Proxy!

Spring erstellt PROXY für RequestContext
Proxy leitet Aufrufe an aktuelle Request-Instanz weiter

PersonService (Singleton)
    ↓ bekommt injiziert
RequestContext-PROXY (Singleton)
    ↓ leitet weiter an
RequestContext-Instanz (Request-Scoped, pro Request neu)

4.2 Request Context nutzen

@Service
@RequiredArgsConstructor
public class PersonService {
    
    private final RequestContext requestContext;  // Proxy!
    
    public Person createPerson(Person person) {
        log.info("Creating person in request: {}", requestContext.getRequestId());
        log.info("User: {}", requestContext.getUserName());
        
        person.setId(idCounter.getAndIncrement());
        person.setCreatedBy(requestContext.getUserName());
        person.setCreatedAt(requestContext.getRequestTime());
        
        persons.add(person);
        return person;
    }
}

Jeder Request hat seinen eigenen RequestContext! ✅


Schritt 5: Session Scope – Pro User Session (2 Stunden)

5.1 🔗 Von JSP/Servlets zu Spring Boot – Die Brücke

WICHTIG: Du kennst Session Scope schon aus Java Web Basic!

Erinnerst du dich an den Pizza Service? Dort hast du Session Scope händisch verwaltet:

// ===== JAVA WEB BASIC (JSP/Servlets) =====
// Pizza Service - Session manuell verwalten

@WebServlet("/cart")
public class CartServlet extends HttpServlet {
    
    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
        // Session MANUELL holen/erstellen
        HttpSession session = request.getSession();
        
        // Cart aus Session holen oder neu erstellen
        PizzaCart cart = (PizzaCart) session.getAttribute("cart");
        if (cart == null) {
            cart = new PizzaCart();
            session.setAttribute("cart", cart);
        }
        
        // Pizza hinzufügen
        String pizzaName = request.getParameter("pizza");
        cart.addPizza(pizzaName);
        
        // Cart MANUELL zurück in Session
        session.setAttribute("cart", cart);
    }
}

Das Problem:

  • ❌ Viel Boilerplate-Code
  • ❌ Null-Checks überall
  • ❌ getAttribute/setAttribute manuell
  • ❌ Session-Management händisch

Spring Boot macht dasselbe – AUTOMATISCH:

// ===== SPRING BOOT (Moderne Variante) =====
// Shopping Cart - Spring verwaltet Session

@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, 
       proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ShoppingCart {
    
    private final List<CartItem> items = new ArrayList<>();
    
    @PostConstruct
    public void init() {
        log.info("Created ShoppingCart for session");
    }
    
    public void addItem(String productName, int quantity, double price) {
        items.add(new CartItem(productName, quantity, price));
    }
    
    public List<CartItem> getItems() {
        return new ArrayList<>(items);
    }
}

// Controller nutzt Cart - KEIN Session-Code nötig!
@Controller
@RequiredArgsConstructor
public class ShopController {
    
    private final ShoppingCart cart;  // Spring injiziert automatisch!
    
    @PostMapping("/cart/add")
    public String addToCart(@RequestParam String product) {
        cart.addItem(product, 1, 29.99);  // Kein session.getAttribute()!
        return "redirect:/cart";
    }
}

Das Konzept bleibt GLEICH:

KonzeptJSP/ServletsSpring Boot
Session erstellenrequest.getSession()Spring macht automatisch
Objekt in Sessionsession.setAttribute("cart", cart)@Scope("session")
Objekt aus Session(Cart) session.getAttribute("cart")Spring injiziert automatisch
Null-Checkif (cart == null) { ... }Spring garantiert Bean existiert
Session-Timeoutsession.setMaxInactiveInterval()server.servlet.session.timeout

Das ist KEIN neues Konzept – nur elegantere Implementation!

Du machst Migration-Ready Training! 🚀

5.2 Session Scope im Person-Projekt nutzen

Praktisches Beispiel: „Meine Personen“ Favoriten

@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, 
       proxyMode = ScopedProxyMode.TARGET_CLASS)
public class PersonFavorites {
    
    private String sessionId;
    private final Set<Long> favoritePersonIds = new HashSet<>();
    
    @PostConstruct
    public void init() {
        this.sessionId = UUID.randomUUID().toString();
        log.info("Created PersonFavorites for session: {}", sessionId);
    }
    
    @PreDestroy
    public void cleanup() {
        log.info("Destroying PersonFavorites for session: {}", sessionId);
    }
    
    public void addFavorite(Long personId) {
        favoritePersonIds.add(personId);
        log.info("Session {}: Added person {} to favorites", sessionId, personId);
    }
    
    public void removeFavorite(Long personId) {
        favoritePersonIds.remove(personId);
        log.info("Session {}: Removed person {} from favorites", sessionId, personId);
    }
    
    public boolean isFavorite(Long personId) {
        return favoritePersonIds.contains(personId);
    }
    
    public Set<Long> getAllFavorites() {
        return new HashSet<>(favoritePersonIds);
    }
    
    public int getFavoriteCount() {
        return favoritePersonIds.size();
    }
}

Controller erweitern:

@Controller
@RequestMapping("/persons")
@RequiredArgsConstructor
public class PersonViewController {
    
    private final PersonService personService;
    private final PersonFavorites favorites;  // Session-Scoped!
    
    @GetMapping
    public String listPersons(Model model) {
        List<Person> persons = personService.getAllPersons();
        
        model.addAttribute("persons", persons);
        model.addAttribute("favorites", favorites);  // Für Thymeleaf
        model.addAttribute("favoriteCount", favorites.getFavoriteCount());
        
        return "persons-list";
    }
    
    @PostMapping("/favorite/add")
    public String addToFavorites(@RequestParam Long personId) {
        favorites.addFavorite(personId);
        return "redirect:/persons";
    }
    
    @PostMapping("/favorite/remove")
    public String removeFromFavorites(@RequestParam Long personId) {
        favorites.removeFavorite(personId);
        return "redirect:/persons";
    }
}

5.3 Session Scope testen

Test 1: Verschiedene Browser = Verschiedene Sessions

Browser 1 (Chrome):
1. http://localhost:8080/persons
2. Markiere Person 1 als Favorit
3. Markiere Person 3 als Favorit
→ Favoriten: 2

Browser 2 (Firefox):
1. http://localhost:8080/persons
2. Markiere Person 2 als Favorit
→ Favoriten: 1

Browser 1 aktualisieren:
→ Favoriten immer noch: 2 (Person 1 und 3)!

Jeder Browser hat seine eigene Session = eigene Favoriten! ✅


✅ Checkpoint Professional

Kontrolliere:

  • [ ] Du verstehst Request Scope mit RequestContext
  • [ ] Du kennst Scoped Proxies und weißt warum sie wichtig sind
  • [ ] Du siehst die Brücke von JSP/Servlets zu Spring Boot
  • [ ] Du verstehst Session Scope konzeptionell
  • [ ] PersonFavorites mit Session Scope ist implementiert
  • [ ] Du kannst verschiedene Sessions testen

Alles ✅? Weiter zu 🔵 Enterprise Features!


🔵 BONUS: ENTERPRISE FEATURES (Schritte 6-7)

Schritt 6: Application Scope & Scope-Mix-Fehler (1 Stunde)

6.1 Application Scope verstehen

Use Case: App-weite Statistiken, Konfiguration, Cache

@Component
@Scope(value = WebApplicationContext.SCOPE_APPLICATION)
public class ApplicationStatistics {
    
    private final AtomicLong totalRequests = new AtomicLong(0);
    private final AtomicLong totalPersonsCreated = new AtomicLong(0);
    private final LocalDateTime startTime = LocalDateTime.now();
    
    @PostConstruct
    public void init() {
        log.info("ApplicationStatistics initialized");
    }
    
    public void incrementRequests() {
        totalRequests.incrementAndGet();
    }
    
    public void incrementPersonsCreated() {
        totalPersonsCreated.incrementAndGet();
    }
    
    public long getTotalRequests() {
        return totalRequests.get();
    }
    
    public Duration getUptime() {
        return Duration.between(startTime, LocalDateTime.now());
    }
}

Alle User sehen dieselben Statistiken! ✅

6.2 ⚠️ WARNUNG: Session-Scoped Services – Häufiger Fehler!

WICHTIGE FRAGE: Was passiert wenn du einen @Service im Session Scope legst?

// ❌ VORSICHT: PersonService im Session Scope!
@Service
@Scope(value = WebApplicationContext.SCOPE_SESSION, 
       proxyMode = ScopedProxyMode.TARGET_CLASS)
public class PersonService {
    
    private final List<Person> persons = new ArrayList<>();
    
    public void addPerson(Person person) {
        persons.add(person);
    }
}

Was passiert?

User A fügt Person "Max" hinzu
  → User A's PersonService-Instanz hat: [Max]

User B fügt Person "Anna" hinzu
  → User B's PersonService-Instanz hat: [Anna]

User A ruft getAllPersons()
  → Sieht nur [Max], NICHT Anna! 😱

Das Problem: Jeder User hat seine EIGENE PersonService-Instanz = EIGENE Personen-Liste!

Die Faustregel:

ZweckScopeBeispiel
Globale Daten (alle User teilen)SingletonPersonService, PersonRepository
Zustandslose Business-LogikSingletonEmailService, ValidationService
User-spezifische DatenSessionUserCart, UserPreferences, PersonFavorites

💡 Merksatz:

„Daten können Session-Scoped sein, Logik sollte Singleton sein!“


Schritt 7: Best Practices & Migration-Patterns (1 Stunde)

7.1 Wann welchen Scope?

Use CaseScopeWarum?
Zustandsloser ServiceSingletonPerformance, keine User-Daten
RepositorySingletonDatenbankzugriff ist zustandslos
ControllerSingletonMVC-Pattern, kein State
Builder mit StatePrototypeJede Instanz eigener State
Request-TrackingRequestPro HTTP-Request neue Instanz
User-spezifische DatenSessionPizza Cart, Favoriten, Settings
App-weite StatistikenApplicationAlle User sehen dieselben Daten

7.2 Migration-Patterns: Legacy → Modern

Pattern 1: Session-Attribute → Session-Scoped Bean

// ❌ LEGACY (JSP/Servlets)
HttpSession session = request.getSession();
PizzaCart cart = (PizzaCart) session.getAttribute("cart");
if (cart == null) {
    cart = new PizzaCart();
    session.setAttribute("cart", cart);
}

// ✅ MODERN (Spring Boot)
@Autowired
private ShoppingCart cart;  // Spring verwaltet automatisch!

7.3 Thread-Safety Regeln

ScopeThread-SafetyWarum?
Singleton⚠️ MUSS thread-safe sein!Alle Threads greifen zu
Prototype✅ Automatisch thread-safeJeder Thread eigene Instanz
Request✅ Automatisch thread-safeEin Request = Ein Thread
Session⚠️ Thread-safe machen!Mehrere Requests parallel
Application⚠️ MUSS thread-safe sein!Alle Threads greifen zu

✅ Checkpoint Enterprise Features

Kontrolliere:

  • [ ] Application Scope für Statistiken implementiert
  • [ ] Du verstehst Scope-Mix-Fehler (@Service im Session Scope)
  • [ ] Du kennst die Faustregel: Daten = Session, Logik = Singleton
  • [ ] Du verstehst Migration-Patterns von Legacy zu Modern
  • [ ] Du kennst Thread-Safety Regeln für Scopes
  • [ ] Du kannst Best Practices für Production-Apps anwenden

Alles ✅? Du bist jetzt ein Scope-Profi! 🎉

Hier sind noch einige nützliche externe Links zum Thema Scopes in Spring Framework bzw. Spring Boot-Beans, die sich gut für dein Buch-Kapitel eignen:

  • „Bean Scopes :: Spring Framework“ — Offizielle Dokumentation mit Übersicht über die Standard-Scopes (singleton, prototype, request, session, application, websocket) und wie man eigene Scopes definiert. Home
  • „Quick Guide to Spring Bean Scopes“ bei Baeldung — Eine gut verständliche Einführung mit Code-Beispielen zu den verschiedenen Scopes. Baeldung on Kotlin
  • „Singleton and Prototype Bean Scopes in Java Spring“ bei GeeksforGeeks — Fokus auf die beiden häufigsten Scopes (singleton, prototype) mit Vergleich und praktischem Beispiel. GeeksforGeeks
  • „When to use Spring Prototype vs Request vs Session scope? Any real world example“ (Frage/Antwort bei Stack Overflow) — Diskussion über reale Anwendungsfälle und Fallstricke bei der Wahl der Scope. Stack Overflow
  • „Spring Bean Scopes Explained: Impact on Memory and Garbage Collection“ (Medium-Artikel) — Vertieft, wie die Wahl des Scopes Speicher- und Lebenszyklusverhalten beeinflusst. Medium

🔥 Elyndras Real Talk:

Nova kam heute zu mir: „Elyndra, ich hab jetzt verstanden warum wir im Java Web Kurs Pizza Service mit Session gemacht haben. Das ist dasselbe Konzept wie hier, nur dass Spring es automatisch macht.“

Ich nickte: „Genau. Das ist der Grund warum du beide Welten lernen musst. Lass mich dir eine Geschichte erzählen…“

Die AutoTech Singleton-Katastrophe & Migration-Story

Das war 2019, mein zweites Jahr bei AutoTech. Wir hatten einen PaymentService – Singleton natürlich. Legacy-Code von 2010, noch mit Spring 2.5.

@Service  // Singleton!
public class PaymentService {
    
    private PaymentRequest currentRequest;  // SHARED STATE!
    
    public void processPayment(PaymentRequest request) {
        this.currentRequest = request;
        
        // 2 Sekunden Verarbeitung...
        Thread.sleep(2000);
        
        // Payment mit currentRequest abschließen
        finishPayment(currentRequest);
    }
}

Was schief ging:

10:00:00.000 - User A startet Payment (€100)
               → currentRequest = Request A (€100)

10:00:01.000 - User B startet Payment (€500)
               → currentRequest = Request B (€500) [ÜBERSCHREIBT A!]

10:00:02.000 - User A's Payment wird abgeschlossen
               → Aber mit Request B's Daten!
               → User A zahlt €500 statt €100! 😱

Das Ergebnis: 47 Kunden bekamen falsche Beträge abgebucht. Production-Down für 3 Stunden. Code Sentinel musste Überstunden schieben um Rollbacks zu machen.

Die Migration-Lösung? Request-Scoped Bean!

@Service
@Scope(value = WebApplicationContext.SCOPE_REQUEST,
       proxyMode = ScopedProxyMode.TARGET_CLASS)
public class PaymentContext {
    
    private PaymentRequest request;  // Pro Request!
}

@Service
@RequiredArgsConstructor
public class PaymentService {
    
    private final PaymentContext context;  // Proxy!
    
    public void processPayment(PaymentRequest request) {
        context.setRequest(request);  // In DIESEM Request gespeichert
        // ...
        finishPayment(context.getRequest());  // Richtige Request!
    }
}

Nova war nachdenklich: „Jetzt verstehe ich warum wir im Java Web Kurs erst die Legacy-Variante lernen. Wenn ich nur Spring Boot kenne, verstehe ich nicht warum Spring das so macht.“

„Exakt,“ sagte ich. „Das ist der Unterschied zwischen ‚Code kopieren‘ und ‚Migration verstehen‘. Legacy-Code verstehen bedeutet Migration können.“

Code Sentinel kam dazu: „70% der Enterprise-Projekte sind Migration-Projekte. Wenn du nur Spring Boot kannst, bist du für diese Jobs nicht geeignet. Wenn du JSP/Servlets UND Spring Boot verstehst, bist du wesentlich wertvoller.“

Das ist die Realität:

  • JSP/Servlets lehren: Das KONZEPT verstehen
  • Spring Boot zeigen: Die MODERNE Implementation
  • Beide zusammen: Migration-Ready Entwickler!

🆘 Troubleshooting: Die häufigsten Probleme

Problem 1: „Scope ‚request‘ is not active“

Symptom:

Error creating bean with name 'requestContext': Scope 'request' is not active

Ursache: Bean wird außerhalb eines HTTP-Requests erstellt

Lösung: Scoped Proxy nutzen!

@Scope(value = WebApplicationContext.SCOPE_REQUEST, 
       proxyMode = ScopedProxyMode.TARGET_CLASS)  // WICHTIG!
public class RequestContext {

Problem 2: Prototype wird nur einmal erstellt

Symptom: Prototype Bean verhält sich wie Singleton

Ursache: Prototype in Singleton injiziert = einmalige Injection!

Richtig:

@Service
@RequiredArgsConstructor
public class MyService {
    
    private final ObjectFactory<PrototypeBean> prototypeBeanFactory;
    
    public void doSomething() {
        PrototypeBean bean = prototypeBeanFactory.getObject();  // NEU!
    }
}

Problem 3: Thread-Safety bei Singleton

Symptom: Race Conditions, falsche Daten

Richtig:

@Service
public class CounterService {
    
    private final AtomicInteger counter = new AtomicInteger(0);  // Thread-safe!
    
    public void increment() {
        counter.incrementAndGet();
    }
}

❓ FAQ (Häufige Fragen)

Q: Was ist der DEFAULT Scope?
A: Singleton! Wenn du kein @Scope angibst, ist die Bean Singleton. Das ist für 90% der Beans richtig.

Q: Wann Singleton, wann Prototype?
A: Singleton für zustandslose Services (PersonService, EmailService). Prototype für Objekte mit temporärem State (Builder, Command-Pattern).

Q: Brauche ich wirklich Scoped Proxies?
A: Ja, wenn du Request/Session Beans in Singleton Beans injizierst! Ohne Proxy bekommst du „Scope not active“ Fehler.

Q: Ist Session Scope dasselbe wie im Pizza Service (Java Web)?
A: Ja, das KONZEPT ist identisch! JSP/Servlets: session.getAttribute/setAttribute. Spring Boot: @Scope("session"). Beides macht: Pro User eine Instanz.

Q: Warum lernen wir erst JSP/Servlets, dann Spring Boot?
A: Weil 70% der Enterprise-Projekte Migration-Projekte sind! Du musst Legacy verstehen um migrieren zu können. Das macht dich 2x wertvoller auf dem Arbeitsmarkt!

Q: Warum lernen wir @Repository nicht im Basic-Kurs?
A: Weil Data-Access eine eigene komplexe Schicht ist! @Repository arbeitet mit JPA/Hibernate, Transactions, SQL – das sind 2-3 eigene Kurstage. Im Basic-Kurs fokussieren wir auf Spring-Grundlagen (DI, Scopes, AOP) mit ArrayList (In-Memory). Schicht für Schicht lernen!

Q: Kann ich @Service im Session Scope nutzen?
A: Technisch ja, aber meistens FALSCH! Services sollten Singleton sein (zustandslos, wiederverwendbar). Session Scope nur für User-spezifische Daten (UserCart, UserPreferences), NICHT für Business-Logik. Faustregel: Daten = Session, Logik = Singleton!

Q: Was passiert mit eurem Team nach Projekt-Ende?
A: Das… ist kompliziert. Manche Geschichten gehören nicht hierhin. Aber vielleicht in private logs. 🔒


📅 Nächster Kurstag: Tag 8

Morgen im Kurs / Nächster Blogbeitrag:

„WebSockets in Spring Boot – Real-Time Communication“

Was du lernen wirst:

  • WebSocket Basics verstehen
  • @MessageMapping und STOMP
  • Broadcasting und Point-to-Point Messages
  • Live Chat-Application bauen
  • WebSocket Security
  • Fallback-Strategien (SockJS)

Dauer: 8 Stunden
Voraussetzung: Tag 7 abgeschlossen

👉 Zum Blogbeitrag Tag 8 (erscheint morgen)


📚 Deine Fortschritts-Übersicht

TagThemaStatus
✅ 1Erste REST APIABGESCHLOSSEN! 🎉
✅ 2Spring Container & DIABGESCHLOSSEN! 🎉
✅ 3@Controller & Thymeleaf BasicsABGESCHLOSSEN! 🎉
✅ 4Thymeleaf Forms & MVC-PatternABGESCHLOSSEN! 🎉
✅ 5Konfiguration & LoggingABGESCHLOSSEN! 🎉
✅ 6DI & AOP im DetailABGESCHLOSSEN! 🎉
✅ 7Scopes in SpringABGESCHLOSSEN! 🎉
→ 8WebSocketsAls nächstes
9JAX-RS in Spring BootNoch offen
10Integration & AbschlussNoch offen

Du hast 70% des Kurses geschafft! 💪

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


📥 Download & Ressourcen

Projekt zum Download:
👉 SpringBootScopes-v1.0.zip (Stand: 19.10.2025)

Was ist im ZIP enthalten:

  • ✅ Komplettes Maven-Projekt
  • ✅ Alle 5 Scope-Beispiele
  • ✅ Person Favorites (Session Scope)
  • ✅ Request Context mit Filter
  • ✅ Application Statistics
  • ✅ Thymeleaf Templates
  • ✅ README mit Schnellstart
  • ✅ SCOPES-GUIDE.md mit Best Practices
  • ✅ MIGRATION.md – Von JSP/Servlets zu Spring Boot

Projekt starten:

# ZIP entpacken
# In NetBeans öffnen: File → Open Project
# Oder im Terminal:
mvn spring-boot:run -Dspring-boot.run.profiles=dev

# Testen:
http://localhost:8080/persons
http://localhost:8080/admin/stats

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


Das war Tag 7 von Spring Boot Basic!

Du kannst jetzt:

  • ✅ Die Semantik von @Service, @Controller, @Component verstehen
  • ✅ Architektur-Regeln für Scopes anwenden
  • ✅ Alle Bean Scopes verstehen und einsetzen
  • ✅ Singleton vs Prototype korrekt wählen
  • ✅ Request und Session Scopes nutzen
  • ✅ Person Favorites mit Session Scope bauen
  • ✅ Application-weite Statistiken implementieren
  • ✅ Scoped Proxies verstehen
  • ✅ Die Brücke von JSP/Servlets zu Spring Boot sehen
  • ✅ Migration-Ready entwickeln! 🚀

Morgen: WebSockets – Real-Time Communication mit Spring! 🚀

Keep coding, keep learning! 💙


Tag 8 erscheint morgen. Bis dahin: Happy Coding!


Tags: #SpringBoot #Scopes #Singleton #Prototype #SessionScope #RequestScope #Migration #LegacyToModern #Tag7

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.