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

📍 Deine Position im Kurs
| Tag | Thema | Status |
|---|---|---|
| ✅ 1 | Erste REST API | Abgeschlossen |
| ✅ 2 | Spring Container & DI | Abgeschlossen |
| ✅ 3 | @Controller & Thymeleaf Basics | Abgeschlossen |
| ✅ 4 | Thymeleaf Forms & MVC-Pattern | Abgeschlossen |
| ✅ 5 | Konfiguration & Logging | Abgeschlossen |
| ✅ 6 | DI & AOP im Detail | Abgeschlossen |
| → 7 | Scopes in Spring | 👉 DU BIST HIER! |
| 8 | WebSockets | Noch nicht freigeschaltet |
| 9 | JAX-RS in Spring Boot | Noch nicht freigeschaltet |
| 10 | Integration & Abschluss | Noch 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):
| Annotation | Layer | Aufgabe | Scope | Im Basic-Kurs? |
|---|---|---|---|---|
| @Component | Keine spezifische | Utilities, State-Holder | FLEXIBEL | ✅ JA |
| @Service | Business Logic | Geschäftslogik | Singleton | ✅ JA |
| @Controller | Presentation (MVC) | Request → View | Singleton | ✅ JA |
| @RestController | REST API | Request → JSON | Singleton | ✅ JA |
| @Repository | Data Access | CRUD, DB-Zugriff | Singleton | ⏰ 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
| Scope | Wann erstellt? | Lebensdauer | Verwendung |
|---|---|---|---|
| singleton | EINMAL beim Start | Bis App-Ende | DEFAULT! Services, Repositories |
| prototype | JEDES MAL bei @Autowired | Bis Garbage Collection | Temporäre Objekte |
| request | PRO HTTP-Request | Bis Request fertig | Request-spezifische Daten |
| session | PRO HTTP-Session | Bis Session-Timeout | User-spezifische Daten |
| application | EINMAL pro ServletContext | Bis App-Ende | Globale 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?
| Szenario | Singleton oder Prototype? |
|---|---|
| Zustandsloser Service | Singleton |
| Repository | Singleton |
| Controller | Singleton |
| Temporäre Berechnungen mit State | Prototype |
| Builder-Pattern | Prototype |
| Command-Pattern | Prototype |
✅ 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:
| Konzept | JSP/Servlets | Spring Boot |
|---|---|---|
| Session erstellen | request.getSession() | Spring macht automatisch |
| Objekt in Session | session.setAttribute("cart", cart) | @Scope("session") |
| Objekt aus Session | (Cart) session.getAttribute("cart") | Spring injiziert automatisch |
| Null-Check | if (cart == null) { ... } | Spring garantiert Bean existiert |
| Session-Timeout | session.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:
| Zweck | Scope | Beispiel |
|---|---|---|
| Globale Daten (alle User teilen) | Singleton | PersonService, PersonRepository |
| Zustandslose Business-Logik | Singleton | EmailService, ValidationService |
| User-spezifische Daten | Session | UserCart, 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 Case | Scope | Warum? |
|---|---|---|
| Zustandsloser Service | Singleton | Performance, keine User-Daten |
| Repository | Singleton | Datenbankzugriff ist zustandslos |
| Controller | Singleton | MVC-Pattern, kein State |
| Builder mit State | Prototype | Jede Instanz eigener State |
| Request-Tracking | Request | Pro HTTP-Request neue Instanz |
| User-spezifische Daten | Session | Pizza Cart, Favoriten, Settings |
| App-weite Statistiken | Application | Alle 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
| Scope | Thread-Safety | Warum? |
|---|---|---|
| Singleton | ⚠️ MUSS thread-safe sein! | Alle Threads greifen zu |
| Prototype | ✅ Automatisch thread-safe | Jeder Thread eigene Instanz |
| Request | ✅ Automatisch thread-safe | Ein 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
| Tag | Thema | Status |
|---|---|---|
| ✅ 1 | Erste REST API | ABGESCHLOSSEN! 🎉 |
| ✅ 2 | Spring Container & DI | ABGESCHLOSSEN! 🎉 |
| ✅ 3 | @Controller & Thymeleaf Basics | ABGESCHLOSSEN! 🎉 |
| ✅ 4 | Thymeleaf Forms & MVC-Pattern | ABGESCHLOSSEN! 🎉 |
| ✅ 5 | Konfiguration & Logging | ABGESCHLOSSEN! 🎉 |
| ✅ 6 | DI & AOP im Detail | ABGESCHLOSSEN! 🎉 |
| ✅ 7 | Scopes in Spring | ABGESCHLOSSEN! 🎉 |
| → 8 | WebSockets | Als nächstes |
| 9 | JAX-RS in Spring Boot | Noch offen |
| 10 | Integration & Abschluss | Noch 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

