Java Web Basic – Tag 4 von 10
Von Elyndra Valen, Senior Developer bei Java Fleet Systems Consulting
🗺️ Deine Position im Kurs
| Tag | Thema | Status |
|---|---|---|
| 1 | Java EE Überblick & HTTP | ✅ Abgeschlossen |
| 2 | HTTP-Protokoll Vertiefung & Zustandslosigkeit | ✅ Abgeschlossen |
| 3 | Servlets & Servlet API | ✅ Abgeschlossen |
| → 4 | Deployment Descriptor & MVC vs Model 2 | 👉 DU BIST HIER! |
| 5 | JSP & Expression Languages (Teil 1) | 🔜 Kommt als nächstes |
| 6 | Java Beans, Actions, Scopes & Direktiven | 🔒 Noch nicht freigeschaltet |
| 7 | Include-Action vs Include-Direktive | 🔒 Noch nicht freigeschaltet |
| 8 | JSTL – Java Standard Tag Libraries | 🔒 Noch nicht freigeschaltet |
| 9 | Java Web und Datenbanken – Datasource | 🔒 Noch nicht freigeschaltet |
| 10 | Connection Pools & JDBC in Web-Umgebungen | 🔒 Noch nicht freigeschaltet |
Modul: Java Web Basic
Gesamt-Dauer: 10 Arbeitstage (je 8 Stunden)
Dauer heute: 8 Stunden
Dein Ziel: Deployment Deskriptor verstehen und saubere MVC-Architektur implementieren
📋 Voraussetzungen für diesen Tag
Du brauchst:
- ✅ JDK 21 LTS installiert
- ✅ Apache NetBeans 22 (oder neuer)
- ✅ Payara Server 6.x konfiguriert und lauffähig
- ✅ Tag 1-3 abgeschlossen (HTTP-Grundlagen, Servlets verstanden)
- ✅ Verständnis für Servlet Lifecycle
- ✅ Erfahrung mit Annotations (
@WebServlet)
Tag verpasst?
Spring zurück zu Tag 3, um die Servlet API zu verstehen. Ohne dieses Wissen wird heute schwer!
Setup-Probleme?
Schreib uns: support@java-developer.online
⚡ Das Wichtigste in 30 Sekunden
Heute lernst du:
- ✅ Was der Deployment Descriptor (web.xml) ist und wann du ihn brauchst
- ✅ Unterschied zwischen Annotations und XML-Konfiguration
- ✅ Was MVC (Model-View-Controller) bedeutet
- ✅ Was Model 2 ist und warum es besser als Model 1 ist
- ✅ Wie du saubere Webarchitekturen designst
- ✅ Best Practices für Servlet-Strukturierung
Am Ende des Tages kannst du:
- web.xml schreiben und verstehen
- Entscheiden wann Annotations vs. XML sinnvoll ist
- MVC Pattern in Java Web umsetzen
- Spaghetti-Code vermeiden
- Eine saubere, wartbare Webanwendung strukturieren
Zeit-Investment: ~8 Stunden
Schwierigkeitsgrad: Mittel (Architektur-Fokus!)
👋 Willkommen zu Tag 4!
Hi! 👋
Elyndra hier. Heute wird es architektonisch!
Kurzwiederholung: Challenge von Tag 3
Gestern solltest du ein Servlet erstellen, das „Hello, [Name]!“ ausgibt. Falls du es noch nicht gemacht hast, hier die Musterlösung:
package com.example;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.*;
import java.io.IOException;
@WebServlet("/greet")
public class GreetingServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// Parameter auslesen
String name = request.getParameter("name");
// Fallback wenn kein Name angegeben
if (name == null || name.trim().isEmpty()) {
name = "Stranger";
}
// Content-Type setzen
response.setContentType("text/html;charset=UTF-8");
// Response schreiben
response.getWriter().println("<html>");
response.getWriter().println("<body>");
response.getWriter().println("<h1>Hello, " + name + "!</h1>");
response.getWriter().println("</body>");
response.getWriter().println("</html>");
}
}
Testen mit:
http://localhost:8080/HelloPayara/greet?name=Anna
Gut gemacht! 🎉
Warum Architektur wichtig ist:
In den letzten drei Tagen hast du gelernt, WIE Servlets funktionieren. Heute lernst du, wie du sie RICHTIG strukturierst.
Das ist wie beim Hausbau: Du kannst Wände hochziehen – aber wenn du nicht weißt, wo tragende Wände hin müssen, wird dein Haus instabil.
Als ich vor Jahren mein erstes Webprojekt gebaut habe, hatte ich überall Java-Code in HTML gemischt. Das funktionierte – für zwei Wochen. Dann wollte ich eine Änderung machen und habe drei Tage gebraucht, um herauszufinden, wo was ist.
Das ist Spaghetti-Code.
Und heute lernst du, wie du ihn vermeidest.
Was dich heute erwartet:
Wir schauen uns zwei große Themen an:
- Deployment Descriptor (web.xml) – Die zentrale Konfigurationsdatei
- MVC vs. Model 2 – Architektur-Pattern für saubere Webapps
Beide Themen sind fundamental. Spring Boot versteckt vieles davon vor dir – aber es ist TROTZDEM da. Und wenn du es nicht verstehst, wirst du bei Problemen hilflos sein.
Keine Sorge:
Ich erkläre dir alles Schritt für Schritt. Mit Code-Beispielen, Diagrammen und konkreten Use-Cases.
Los geht’s! 🏗️
🟢 GRUNDLAGEN: Der Deployment Descriptor (web.xml)
Was ist web.xml?
Definition:
Der Deployment Descriptor ist eine XML-Datei namens web.xml, die im Verzeichnis WEB-INF/ einer Java Webanwendung liegt.
Sie beschreibt:
- Konfiguration der Webanwendung
- Servlet-Mappings
- Filter
- Listener
- Security-Constraints
- Error-Pages
- Session-Konfiguration
- Welcome-Files
Wichtig zu verstehen:
Seit Servlet 3.0 (2009) sind Annotations (@WebServlet, @WebFilter etc.) verfügbar. Das bedeutet: Viele Dinge, die früher in web.xml stehen mussten, können jetzt per Annotation direkt in der Java-Klasse definiert werden.
Aber:
web.xml ist NICHT tot! Es gibt Szenarien, wo XML besser ist als Annotations.
Historischer Kontext: Vor Servlet 3.0
So sah es früher aus (Servlet 2.5 und älter):
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<!-- Display Name -->
<display-name>My Web Application</display-name>
<!-- Servlet Definition -->
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.example.HelloServlet</servlet-class>
</servlet>
<!-- Servlet Mapping -->
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
Was passiert hier?
- Servlet Definition (
<servlet>):- Gibt dem Servlet einen internen Namen:
HelloServlet - Definiert die Java-Klasse:
com.example.HelloServlet
- Gibt dem Servlet einen internen Namen:
- Servlet Mapping (
<servlet-mapping>):- Verknüpft den internen Namen mit einem URL-Pattern
- URL
/hello→HelloServlet
Das Problem:
Du musstest für JEDES Servlet:
- Die Java-Klasse schreiben
- In
web.xmldas Servlet definieren - In
web.xmldas Mapping definieren
Das war mühsam und fehleranfällig!
Servlet 3.0+: Annotations vs. XML
Seit Servlet 3.0 geht es so:
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.getWriter().println("Hello from Annotation!");
}
}
Das war’s!
Keine web.xml nötig. Die Annotation @WebServlet("/hello") erledigt beides:
- Servlet-Definition
- URL-Mapping
Viel einfacher, oder?
Wann brauchst du trotzdem web.xml?
Szenarien, wo XML besser ist:
Szenario 1: Zentrale Konfiguration
Du hast 50 Servlets und willst auf einen Blick sehen, welche URLs es gibt:
Mit Annotations:
- Du musst 50 Java-Dateien öffnen
- Jedes Servlet hat sein Mapping irgendwo im Code
- Unübersichtlich!
Mit web.xml:
- EINE Datei
- Alle Mappings auf einen Blick
- Übersichtlich!
Szenario 2: Umgebungs-spezifische Konfiguration
Du willst DEV, TEST und PROD unterschiedlich konfigurieren:
DEV:
<context-param>
<param-name>database.url</param-name>
<param-value>jdbc:mysql://localhost:3306/devdb</param-value>
</context-param>
PROD:
<context-param>
<param-name>database.url</param-name>
<param-value>jdbc:mysql://prod-server:3306/proddb</param-value>
</context-param>
Mit Annotations: Müsstest du Code ändern und neu kompilieren!
Mit XML: Änderst nur die Config-Datei, kein Recompile!
Szenario 3: Legacy-Projekte
Viele alte Projekte verwenden web.xml. Wenn du in so einem Projekt arbeitest, musst du verstehen, wie es funktioniert.
Szenario 4: Framework-Integration
Manche Frameworks (z.B. Spring, Struts) erwarten bestimmte Konfigurationen in web.xml.
web.xml Struktur im Detail
Vollständiges Beispiel:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<!-- Application Display Name -->
<display-name>Shop Application</display-name>
<!-- Description -->
<description>
Online Shop Web Application
</description>
<!-- Context Parameters (global für die ganze App) -->
<context-param>
<param-name>admin.email</param-name>
<param-value>admin@shop.com</param-value>
</context-param>
<context-param>
<param-name>items.per.page</param-name>
<param-value>20</param-value>
</context-param>
<!-- Servlet Definition -->
<servlet>
<servlet-name>ProductServlet</servlet-name>
<servlet-class>com.shop.ProductServlet</servlet-class>
<!-- Init Parameters (nur für dieses Servlet) -->
<init-param>
<param-name>cache.enabled</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Servlet Mapping -->
<servlet-mapping>
<servlet-name>ProductServlet</servlet-name>
<url-pattern>/products</url-pattern>
</servlet-mapping>
<!-- Session Configuration -->
<session-config>
<session-timeout>30</session-timeout> <!-- in Minuten -->
<cookie-config>
<http-only>true</http-only>
<secure>false</secure> <!-- In PROD: true! -->
</cookie-config>
<tracking-mode>COOKIE</tracking-mode>
</session-config>
<!-- Welcome Files -->
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- Error Pages -->
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/errors/404.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/WEB-INF/errors/500.jsp</location>
</error-page>
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/WEB-INF/errors/error.jsp</location>
</error-page>
</web-app>
Was macht jedes Element?
1. Context Parameters
<context-param>
<param-name>admin.email</param-name>
<param-value>admin@shop.com</param-value>
</context-param>
- Scope: Ganze Applikation
- Zugriff in Servlet:
String adminEmail = getServletContext().getInitParameter("admin.email"); - Use-Case: Globale Konfiguration (DB-URLs, Admin-Kontakte, Feature-Flags)
2. Init Parameters (Servlet-spezifisch)
<servlet>
<servlet-name>ProductServlet</servlet-name>
<servlet-class>com.shop.ProductServlet</servlet-class>
<init-param>
<param-name>cache.enabled</param-name>
<param-value>true</param-value>
</init-param>
</servlet>
- Scope: Nur dieses Servlet
- Zugriff in Servlet:
String cacheEnabled = getInitParameter("cache.enabled"); - Use-Case: Servlet-spezifische Config
3. Load-on-Startup
<load-on-startup>1</load-on-startup>
- Wert: Zahl ≥ 0
- Bedeutung: Servlet wird beim Server-Start geladen (nicht bei erstem Request)
- Reihenfolge: Kleinere Zahlen = früher geladen
- Use-Case: Initialisierung (z.B. DB-Connections aufbauen)
4. Session Configuration
<session-config>
<session-timeout>30</session-timeout>
<cookie-config>
<http-only>true</http-only>
<secure>false</secure>
</cookie-config>
</session-config>
- session-timeout: Nach 30 Minuten Inaktivität wird Session invalidiert
- http-only: Cookie kann nicht per JavaScript ausgelesen werden (XSS-Schutz!)
- secure: Cookie nur über HTTPS (in PROD immer
true!)
5. Welcome Files
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
Was passiert bei:
http://localhost:8080/shop/
Server sucht in dieser Reihenfolge:
index.htmlindex.jsp
Erste gefundene Datei wird angezeigt!
6. Error Pages
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/errors/404.jsp</location>
</error-page>
- error-code: HTTP-Status (404, 500, 403, etc.)
- exception-type: Java Exception (
java.lang.Exception) - location: Pfad zu Error-JSP
Best Practice: Custom Error-Pages für bessere UX!
🟡 PROFESSIONALS: web.xml in der Praxis
Best Practice 1: Hybrid-Ansatz (Annotations + XML)
Regel:
- Annotations für Servlet-Mappings (schnell, übersichtlich im Code)
- XML für globale Konfiguration (Session-Config, Error-Pages, Context-Params)
Beispiel:
Java-Klasse:
@WebServlet("/products")
public class ProductServlet extends HttpServlet {
// Servlet-Code hier
}
web.xml:
<web-app ...>
<!-- Globale Config -->
<context-param>
<param-name>database.url</param-name>
<param-value>jdbc:mysql://localhost:3306/shop</param-value>
</context-param>
<!-- Session Config -->
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<!-- Error Pages -->
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/errors/404.jsp</location>
</error-page>
</web-app>
Vorteil: Das Beste aus beiden Welten!
Best Practice 2: Umgebungs-spezifische Konfiguration
Problem: Du willst unterschiedliche Configs für DEV, TEST, PROD.
Lösung: Build-Profile
Maven pom.xml:
<profiles>
<profile>
<id>dev</id>
<properties>
<db.url>jdbc:mysql://localhost:3306/devdb</db.url>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<db.url>jdbc:mysql://prod-db:3306/proddb</db.url>
</properties>
</profile>
</profiles>
web.xml (mit Platzhaltern):
<context-param>
<param-name>database.url</param-name>
<param-value>${db.url}</param-value>
</context-param>
Build:
# DEV mvn clean package -Pdev # PROD mvn clean package -Pprod
Best Practice 3: Security-Constraints
Use-Case: Nur eingeloggte User dürfen /admin/* aufrufen.
<security-constraint>
<web-resource-collection>
<web-resource-name>Admin Area</web-resource-name>
<url-pattern>/admin/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/login.jsp</form-login-page>
<form-error-page>/login-error.jsp</form-error-page>
</form-login-config>
</login-config>
<security-role>
<role-name>admin</role-name>
</security-role>
Was passiert:
- User versucht
/admin/dashboardaufzurufen - Server prüft: Ist User eingeloggt?
- Wenn nein → Redirect zu
/login.jsp - Nach Login → Prüfung der Rolle
- Wenn
admin→ Zugriff erlaubt - Wenn nicht
admin→ 403 Forbidden
Häufige Probleme & Lösungen
Problem 1: Servlet wird nicht gefunden (404)
Mögliche Ursachen:
<!-- FALSCH: Tippfehler in Klasse -->
<servlet-class>com.shop.ProdutServlet</servlet-class>
<!-- FALSCH: Falsches Mapping -->
<servlet-mapping>
<servlet-name>ProductServlet</servlet-name>
<url-pattern>products</url-pattern> <!-- Fehlt: / -->
</servlet-mapping>
<!-- RICHTIG: -->
<servlet-mapping>
<servlet-name>ProductServlet</servlet-name>
<url-pattern>/products</url-pattern>
</servlet-mapping>
Problem 2: XML-Parsing-Fehler beim Deploy
Lösung:
<!-- Prüfe Namespace und Version -->
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<!-- Jakarta EE 10+ verwendet jakarta.* statt javax.* -->
</web-app>
Wichtig:
- Jakarta EE 9+:
jakarta.servlet.* - Java EE 8 und älter:
javax.servlet.*
Problem 3: Context-Parameter wird nicht gefunden
Lösung:
// FALSCH:
String url = getInitParameter("database.url");
// RICHTIG:
String url = getServletContext().getInitParameter("database.url");
Unterschied:
getInitParameter()→ Servlet-Init-ParamgetServletContext().getInitParameter()→ Context-Param (global)
🟢 GRUNDLAGEN: MVC vs. Model 2
Was ist MVC?
MVC = Model-View-Controller
Ein Architektur-Pattern, das eine Anwendung in drei Teile trennt:
┌─────────────────────────────────────────┐
│ USER │
│ (Browser, HTTP Request) │
└─────────────┬───────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ CONTROLLER │
│ (Servlet - empfängt Request) │
│ • Request verarbeiten │
│ • Model aufrufen │
│ • View auswählen │
└─────────────┬───────────────────────────┘
│
├──────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌─────────────────┐
│ MODEL │ │ VIEW │
│ (Business Logic)│ │ (Präsentation) │
│ • Java Beans │ │ • JSP │
│ • DAO Classes │ │ • HTML │
│ • Services │ │ • CSS/JS │
└──────────────────┘ └─────────────────┘
Verantwortlichkeiten:
- Model:
- Enthält Business-Logik
- Daten-Zugriff (DAO)
- Java Beans / POJOs
- KEINE UI-Logik!
- View:
- Präsentiert Daten
- JSPs / HTML
- KEINE Business-Logik!
- Controller:
- Empfängt Requests
- Ruft Model auf
- Wählt View aus
- Koordiniert alles
Model 1 vs. Model 2
In Java Web gibt es zwei MVC-Varianten:
Model 1 (Legacy, nicht empfohlen!)
User → JSP → Java Beans → JSP → User
Ablauf:
- User ruft JSP direkt auf
- JSP enthält Business-Logik (Java-Code!)
- JSP ruft Beans auf
- JSP rendert Ergebnis
Beispiel (Model 1 – NICHT machen!):
<!-- product.jsp -->
<%@ page import="com.shop.*, java.util.*" %>
<%
// Business-Logik in JSP! (SCHLECHT!)
int id = Integer.parseInt(request.getParameter("id"));
ProductDAO dao = new ProductDAO();
Product product = dao.getById(id);
if (product == null) {
response.sendRedirect("error.jsp");
return;
}
%>
<html>
<body>
<h1><%= product.getName() %></h1>
<p><%= product.getDescription() %></p>
<p>Price: $<%= product.getPrice() %></p>
</body>
</html>
Probleme: ❌ Java-Code in JSP (Scriptlets!)
❌ Business-Logik in View
❌ Nicht testbar
❌ Nicht wiederverwendbar
❌ Spaghetti-Code
❌ Designer können nicht arbeiten
Model 2 (Modern, Best Practice!)
User → Servlet (Controller) → Java Beans (Model) → JSP (View) → User
Ablauf:
- User ruft Servlet auf (Controller)
- Servlet verarbeitet Request
- Servlet ruft Model (Beans, DAO) auf
- Servlet setzt Daten in Request-Attribute
- Servlet forwarded zu JSP (View)
- JSP zeigt Daten an (KEIN Java-Code!)
Beispiel (Model 2 – Das ist der Weg!):
Controller: ProductServlet.java
@WebServlet("/product")
public class ProductServlet extends HttpServlet {
private ProductDAO productDAO = new ProductDAO();
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// 1. Request-Parameter lesen
String idParam = request.getParameter("id");
try {
int id = Integer.parseInt(idParam);
// 2. Model aufrufen
Product product = productDAO.getById(id);
if (product == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
// 3. Daten für View vorbereiten
request.setAttribute("product", product);
// 4. View auswählen und forwarden
request.getRequestDispatcher("/WEB-INF/views/product.jsp")
.forward(request, response);
} catch (NumberFormatException e) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST,
"Invalid product ID");
}
}
}
View: WEB-INF/views/product.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<title>${product.name}</title>
</head>
<body>
<h1>${product.name}</h1>
<p>${product.description}</p>
<p>Price: $${product.price}</p>
</body>
</html>
Model: Product.java (Bean)
public class Product {
private int id;
private String name;
private String description;
private double price;
// Constructor, Getters, Setters
}
Model: ProductDAO.java (Data Access)
public class ProductDAO {
public Product getById(int id) {
// DB-Zugriff oder In-Memory-Daten
}
}
Vorteile: ✅ Saubere Trennung (Separation of Concerns)
✅ Testbar (jede Komponente einzeln)
✅ Wiederverwendbar
✅ Wartbar
✅ Team kann parallel arbeiten
✅ Kein Java-Code in JSP!
Model 2 im Detail: Die drei Schichten
1. Controller-Schicht (Servlet)
Verantwortlichkeiten:
- Request-Parameter auslesen
- Validierung
- Model aufrufen
- View auswählen
- Error-Handling
Was der Controller NICHT tut: ❌ Business-Logik (gehört ins Model!)
❌ DB-Zugriff (gehört ins DAO!)
❌ HTML rendern (gehört in View!)
Beispiel:
@WebServlet("/shop")
public class ShopController extends HttpServlet {
private ProductDAO productDAO = new ProductDAO();
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String action = request.getParameter("action");
if (action == null || action.equals("list")) {
listProducts(request, response);
} else if (action.equals("detail")) {
showProduct(request, response);
} else {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
}
}
private void listProducts(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
List<Product> products = productDAO.getAll();
request.setAttribute("products", products);
request.getRequestDispatcher("/WEB-INF/views/product-list.jsp")
.forward(request, response);
}
private void showProduct(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String idParam = request.getParameter("id");
try {
int id = Integer.parseInt(idParam);
Product product = productDAO.getById(id);
if (product == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
request.setAttribute("product", product);
request.getRequestDispatcher("/WEB-INF/views/product-detail.jsp")
.forward(request, response);
} catch (NumberFormatException e) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
}
}
}
2. Model-Schicht (Beans + DAO)
Bean (POJO – Plain Old Java Object):
public class Product {
private int id;
private String name;
private String description;
private double price;
private int stock;
// Constructor
public Product(int id, String name, String description,
double price, int stock) {
this.id = id;
this.name = name;
this.description = description;
this.price = price;
this.stock = stock;
}
// Getters & Setters
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
// ... weitere Getters/Setters
}
DAO (Data Access Object):
public class ProductDAO {
// In-Memory-Daten (in Realität: DB-Zugriff)
private static List<Product> products = Arrays.asList(
new Product(1, "Laptop", "15 inch", 999.99, 10),
new Product(2, "Mouse", "Wireless", 29.99, 50),
new Product(3, "Keyboard", "Mechanical", 89.99, 20)
);
public List<Product> getAll() {
return products;
}
public Product getById(int id) {
return products.stream()
.filter(p -> p.getId() == id)
.findFirst()
.orElse(null);
}
public List<Product> search(String query) {
return products.stream()
.filter(p -> p.getName().toLowerCase()
.contains(query.toLowerCase()))
.collect(Collectors.toList());
}
}
3. View-Schicht (JSP)
JSP zeigt nur Daten an – KEINE Logik!
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<!DOCTYPE html>
<html>
<head>
<title>Products</title>
</head>
<body>
<h1>All Products</h1>
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Price</th>
<th>Stock</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<c:forEach var="product" items="${products}">
<tr>
<td>${product.id}</td>
<td>${product.name}</td>
<td>$${product.price}</td>
<td>${product.stock}</td>
<td>
<a href="shop?action=detail&id=${product.id}">Details</a>
</td>
</tr>
</c:forEach>
</tbody>
</table>
</body>
</html>
🟡 PROFESSIONALS: Model 2 in der Praxis
Best Practice 1: Request-Dispatcher vs. Redirect
Zwei Arten, um zu einer anderen Seite zu wechseln:
Forward (Request-Dispatcher)
request.getRequestDispatcher("/WEB-INF/views/product.jsp")
.forward(request, response);
Eigenschaften:
- Server-seitig (Browser merkt nichts!)
- URL bleibt gleich
- Request-Attribute bleiben erhalten
- Gleicher Request durchläuft mehrere Komponenten
Use-Case: Controller → View (MVC!)
Redirect (sendRedirect)
response.sendRedirect("shop?action=list");
Eigenschaften:
- Client-seitig (Browser macht neuen Request!)
- URL ändert sich
- Request-Attribute gehen verloren
- Neuer Request wird ausgelöst
Use-Case: POST → GET (z.B. nach Formular-Submit)
Wann was verwenden?
| Szenario | Forward | Redirect |
|---|---|---|
| Controller → View | ✅ | ❌ |
| POST → GET (PRG Pattern) | ❌ | ✅ |
| Daten übergeben | ✅ | ❌ |
| URL ändern | ❌ | ✅ |
| Externe URL | ❌ | ✅ |
Best Practice 2: DAO-Pattern
DAO = Data Access Object
Idee: Kapsle DB-Zugriff in eigene Klasse!
Ohne DAO (schlecht!):
@WebServlet("/products")
public class ProductServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// DB-Zugriff direkt im Servlet! (SCHLECHT!)
Connection conn = DriverManager.getConnection(...);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM products");
List<Product> products = new ArrayList<>();
while (rs.next()) {
products.add(new Product(...));
}
request.setAttribute("products", products);
request.getRequestDispatcher("/WEB-INF/views/products.jsp")
.forward(request, response);
}
}
Probleme: ❌ DB-Logik im Controller
❌ Nicht wiederverwendbar
❌ Nicht testbar
❌ Connection-Leaks möglich
Mit DAO (gut!):
ProductDAO.java:
public class ProductDAO {
public List<Product> getAll() {
List<Product> products = new ArrayList<>();
try (Connection conn = DatabaseUtil.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM products")) {
while (rs.next()) {
products.add(new Product(
rs.getInt("id"),
rs.getString("name"),
rs.getString("description"),
rs.getDouble("price"),
rs.getInt("stock")
));
}
} catch (SQLException e) {
throw new RuntimeException("Error fetching products", e);
}
return products;
}
public Product getById(int id) {
// DB-Query mit WHERE id = ?
}
public void save(Product product) {
// INSERT oder UPDATE
}
public void delete(int id) {
// DELETE FROM products WHERE id = ?
}
}
ProductServlet.java:
@WebServlet("/products")
public class ProductServlet extends HttpServlet {
private ProductDAO productDAO = new ProductDAO();
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// Einfach DAO aufrufen!
List<Product> products = productDAO.getAll();
request.setAttribute("products", products);
request.getRequestDispatcher("/WEB-INF/views/products.jsp")
.forward(request, response);
}
}
Vorteile: ✅ Saubere Trennung
✅ Wiederverwendbar
✅ Testbar
✅ Wartbar
✅ Connection-Handling zentral
Best Practice 3: Front Controller Pattern
Problem: Du hast viele URLs:
/products /product/detail /product/add /product/edit /product/delete /categories /category/detail ...
Ohne Front Controller: Ein Servlet pro URL (50 Servlets!)
Mit Front Controller: EIN zentrales Servlet für alles!
@WebServlet("/app/*") // Fängt alle /app/* URLs ab
public class FrontController extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String path = request.getPathInfo(); // z.B. "/products/list"
if (path == null || path.equals("/")) {
// Home
request.getRequestDispatcher("/WEB-INF/views/home.jsp")
.forward(request, response);
return;
}
// Routing basierend auf Path
if (path.startsWith("/products")) {
handleProducts(request, response, path);
} else if (path.startsWith("/categories")) {
handleCategories(request, response, path);
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
private void handleProducts(HttpServletRequest request,
HttpServletResponse response,
String path)
throws ServletException, IOException {
if (path.equals("/products/list")) {
// Liste
} else if (path.equals("/products/detail")) {
// Detail
} else if (path.equals("/products/add")) {
// Hinzufügen
}
// ...
}
}
Vorteil: Zentrale Stelle für Routing!
Häufige Fehler vermeiden
Fehler 1: Business-Logik in JSP
<!-- FALSCH - Business-Logik in View! -->
<%
if (product.getStock() < 10) {
// Reorder-Mail senden
MailService.send(...);
}
%>
Richtig:
// Business-Logik im Servlet oder Service!
if (product.getStock() < 10) {
mailService.sendReorderNotification(product);
}
Fehler 2: Redirect statt Forward für View
// FALSCH - Request-Attribute gehen verloren!
request.setAttribute("product", product);
response.sendRedirect("/product.jsp");
// RICHTIG - Forward behält Attribute!
request.setAttribute("product", product);
request.getRequestDispatcher("/WEB-INF/views/product.jsp")
.forward(request, response);
Fehler 3: JSPs außerhalb von WEB-INF
webapp/
├── product.jsp ← FALSCH (direkt erreichbar!)
└── WEB-INF/
└── views/
└── product.jsp ← RICHTIG (nur via Servlet!)
Warum?
- JSPs in
WEB-INF/sind vom Browser NICHT direkt erreichbar - Erzwingt MVC (Zugriff nur über Controller)
🔵 BONUS: Fortgeschrittene Techniken
Service-Layer Pattern
Erweiterung von MVC:
Controller → Service → DAO → Database
Warum?
- Controller: Nur HTTP-Handling
- Service: Business-Logik
- DAO: Nur DB-Zugriff
Beispiel:
// Service: ProductService.java
public class ProductService {
private ProductDAO productDAO = new ProductDAO();
private MailService mailService = new MailService();
public Product getProduct(int id) {
return productDAO.getById(id);
}
public void purchaseProduct(int productId, int quantity) {
Product product = productDAO.getById(productId);
// Business-Logik
if (product.getStock() < quantity) {
throw new InsufficientStockException();
}
// Stock reduzieren
product.setStock(product.getStock() - quantity);
productDAO.update(product);
// Low-Stock-Warning?
if (product.getStock() < 10) {
mailService.sendLowStockAlert(product);
}
}
}
// Controller: ProductServlet.java
@WebServlet("/product/purchase")
public class ProductPurchaseServlet extends HttpServlet {
private ProductService productService = new ProductService();
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
int productId = Integer.parseInt(request.getParameter("productId"));
int quantity = Integer.parseInt(request.getParameter("quantity"));
try {
productService.purchaseProduct(productId, quantity);
response.sendRedirect("cart?success=true");
} catch (InsufficientStockException e) {
request.setAttribute("error", "Not enough stock");
request.getRequestDispatcher("/WEB-INF/views/error.jsp")
.forward(request, response);
}
}
}
Repository Pattern (modern)
Alternative zu DAO:
public interface ProductRepository {
List<Product> findAll();
Product findById(int id);
List<Product> findByName(String name);
void save(Product product);
void delete(int id);
}
public class JdbcProductRepository implements ProductRepository {
// JDBC-Implementierung
}
public class JpaProductRepository implements ProductRepository {
// JPA-Implementierung
}
Vorteil: Austauschbar (JDBC → JPA → MongoDB)
💬 Real Talk: Architektur im echten Leben
Java Fleet Büro, Kaffeemaschine, 14:30 Uhr. Nova schaut frustriert auf ihren Laptop.
Nova: „Elyndra, ich check das nicht. Warum soll ich MVC verwenden, wenn meine JSP auch einfach direkt auf die Datenbank zugreifen kann? Das ist doch viel schneller programmiert!“
Elyndra: „Lass mich raten – du baust gerade an deinem ersten größeren Projekt?“
Nova: „Ja! Eine kleine Blog-Plattform. Und ehrlich, diese ganzen Controller, DAOs, Services… das fühlt sich an wie Overkill für meine paar Seiten.“
Cassian (kommt mit Kaffeetasse dazu): „Nova, darf ich dir eine Geschichte erzählen?“
Nova: „Oh nein, nicht wieder eine deiner ‚Als ich jung war‘-Geschichten…“
Cassian (lacht): „Relevant, versprochen! Ich hatte vor Jahren ein Projekt – ein kleines Reporting-Tool. ‚Wird eh nicht groß werden‘, dachte ich. Also alles direkt in JSPs gehackt. SQL-Queries, Business-Logik, HTML – alles zusammen. War in 2 Tagen fertig!“
Nova: „Siehst du! Schnell und pragmatisch!“
Cassian: „Ja. Bis… der Chef wollte ’nur noch eine kleine Änderung‘. Dann noch eine. Dann noch eine. Nach 3 Monaten hatte ich 50 JSP-Dateien, überall doppelter Code, und wenn ich EINE Zeile änderte, brach irgendwo anders was.“
Elyndra: „Und dann kam der Refactoring-Horror, richtig?“
Cassian: „Genau. Ich hab das Projekt komplett neu geschrieben – mit MVC. Hat 2 Wochen gedauert. Aber danach? Jede Änderung war easy. Neue Features? Kein Problem. Tests schreiben? Plötzlich möglich!“
Nova: „Okay… aber mein Projekt ist echt klein…“
Elyndra: „Nova, real talk: Projekte bleiben NIE klein. Entweder wächst es, oder du lernst daraus für das nächste Projekt. Und MVC zu lernen ist wie Fahrradfahren – am Anfang wackeliger, aber dann kannst du’s für immer.“
Kofi (kommt vorbei mit Laptop): „Plus, wenn du später im Team arbeitest, erwartet jeder MVC. Stell dir vor, ich muss deinen Code maintainen, und überall ist Business-Logik in JSPs versteckt. Nightmare!“
Nova: „Ugh, okay okay, ihr habt gewonnen. Also wirklich IMMER Controller-Servlet verwenden?“
Elyndra: „Zumindest sobald es mehr als eine statische Info-Seite ist. Regel: Wenn irgendwo Daten aus einer DB kommen oder Formulare verarbeitet werden – MVC. Keine Exceptions.“
Nova: „Und wenn ich wirklich nur eine Seite habe, die ‚Hallo Welt‘ sagt?“
Cassian (lächelt): „Dann kannst du eine statische HTML-Seite machen. Aber lowkey, selbst da würde ich aus Gewohnheit ein Servlet nehmen.“
Nova: „Ihr seid alle verrückt… aber okay, ich probier’s. Worst case habe ich saubereren Code.“
Elyndra: „Best case hast du Code, den du in 6 Monaten noch verstehst. Trust me, future-Nova wird dir danken.“
✅ Checkpoint: Hast du es verstanden?
Zeit für einen Reality-Check! Beantworte diese Fragen, um zu prüfen, ob du heute alles verstanden hast.
Quiz:
Frage 1: Was ist der Hauptzweck des Deployment Descriptors (web.xml)?
Frage 2: Nenne zwei Szenarien, in denen web.xml besser ist als Annotations.
Frage 3: Erkläre den Unterschied zwischen Context-Parametern und Init-Parametern.
Frage 4: Was sind die drei Komponenten von MVC? Was ist die Aufgabe von jeder Komponente?
Frage 5: Was ist der Hauptunterschied zwischen Model 1 und Model 2?
Frage 6: Wann verwendest du Forward und wann Redirect?
Frage 7: Was ist das DAO-Pattern und warum ist es nützlich?
Frage 8: Warum sollten JSPs im WEB-INF Ordner liegen?
🎯 Mini-Challenge
Aufgabe: Erstelle eine einfache Blog-Anwendung mit Model 2 Architektur.
Requirements:
- Model:
BlogPostBean (id, title, content, author, date)BlogPostDAOfür Datenzugriff (In-Memory-Liste reicht)
- Controller:
BlogServletmit URL/blog- Bei
/blog→ Alle Posts anzeigen - Bei
/blog?id=X→ Einen Post anzeigen
- View:
blog-list.jspimWEB-INF/views/Ordnerblog-post.jspimWEB-INF/views/Ordner
- Zusatzaufgabe (optional):
- Nutze Forward im Servlet
- JSPs verwenden EL (
${...}) statt Scriptlets - Error-Handling wenn Post nicht gefunden wird
Hinweise:
- Struktur: Controller → DAO → Model → View
- JSPs im
WEB-INFOrdner (nicht direkt erreichbar!) - Verwende
request.setAttribute()für Datenweitergabe - Verwende
request.getRequestDispatcher(...).forward()für View
Lösung:
Die Lösung zu dieser Challenge findest du am Anfang von Tag 5 als Kurzwiederholung! 🚀
Alternativ kannst du die Musterlösung im GitHub-Projekt checken: https://github.com/java-fleet/java-web-basic-examples
Geschafft? 🎉
Dann bist du bereit für die FAQ-Sektion!
❓ Häufig gestellte Fragen
Frage 1: Muss ich wirklich immer MVC verwenden, auch für kleine Projekte?
Für reine Info-Seiten ohne dynamische Daten? Nein, statisches HTML reicht. Aber sobald du:
- Formulare hast
- Daten aus einer DB lädst
- User-Input verarbeitest
- Session-Management brauchst
…dann JA, verwende MVC! Es mag am Anfang nach Overkill aussehen, aber trust me: Wenn dein „kleines Projekt“ wächst (und das tun sie immer!), wirst du froh sein, saubere Architektur zu haben.
Real talk: Die meisten Legacy-Alpträume entstehen aus „war ja nur ein kleines Projekt“-Entscheidungen.
Frage 2: Annotations oder web.xml – was soll ich nehmen?
Kurze Antwort: Hybrid-Ansatz!
- Annotations für Servlet-Mappings (schnell, code-nah)
- web.xml für globale Konfiguration (Session-Timeout, Error-Pages, Context-Params)
Beispiel:
// Servlet-Mapping: Annotation
@WebServlet("/products")
public class ProductServlet extends HttpServlet { ... }
<!-- Globale Config: web.xml -->
<session-config>
<session-timeout>30</session-timeout>
</session-config>
Vorteil: Das Beste aus beiden Welten!
Frage 3: Wie kann ich verschiedene Configs für DEV, TEST und PROD haben?
Beste Lösung: Maven/Gradle Build-Profile!
Maven pom.xml:
<profiles>
<profile>
<id>dev</id>
<properties>
<db.url>jdbc:mysql://localhost:3306/devdb</db.url>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<db.url>jdbc:mysql://prod-server:3306/proddb</db.url>
</properties>
</profile>
</profiles>
web.xml:
<context-param>
<param-name>database.url</param-name>
<param-value>${db.url}</param-value>
</context-param>
Build:
mvn clean package -Pdev # DEV mvn clean package -Pprod # PROD
Frage 4: Muss ich wirklich ein DAO für jede Entity erstellen?
Für Enterprise-Projekte? Absolut!
Vorteile:
- ✅ Saubere Trennung (Controller kennt keine SQL-Details)
- ✅ Austauschbar (MySQL → PostgreSQL → MongoDB)
- ✅ Testbar (Mock-DAO für Unit-Tests)
- ✅ Wiederverwendbar (mehrere Controller nutzen gleiches DAO)
Für kleine Hobby-Projekte? Du kannst es auch direkter machen – aber sobald es größer wird, wirst du refactoren müssen.
Pro-Tipp: Schreib von Anfang an DAOs. Es dauert 10 Minuten mehr, spart dir aber Tage beim Refactoring!
Frage 5: Forward oder Redirect – wann was?
Forward (Request-Dispatcher):
request.getRequestDispatcher("/view.jsp").forward(request, response);
- ✅ Server-seitig (Browser merkt nichts)
- ✅ URL bleibt gleich
- ✅ Request-Attribute bleiben erhalten
- ✅ Use-Case: Controller → View (MVC!)
Redirect (sendRedirect):
response.sendRedirect("success.jsp");
- ✅ Client-seitig (Browser macht neuen Request)
- ✅ URL ändert sich
- ❌ Request-Attribute gehen verloren
- ✅ Use-Case: POST → GET (verhindert doppelten Submit)
Faustregel:
- Daten zeigen? → Forward
- Nach Formular-Submit? → Redirect (PRG-Pattern!)
- Externe URL? → Redirect
Frage 6: Was passiert, wenn ich ein Servlet sowohl per Annotation als auch in web.xml mappe?
web.xml gewinnt!
@WebServlet("/hello") // Wird ignoriert!
public class HelloServlet extends HttpServlet { ... }
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/greeting</url-pattern> <!-- Das gilt! -->
</servlet-mapping>
Ergebnis: Servlet ist unter /greeting erreichbar, NICHT unter /hello!
Best Practice: Entscheide dich für EINE Methode pro Servlet. Mix führt zu Verwirrung!
Frage 7: Bernd meinte mal, „web.xml ist old school, echte Devs nutzen nur Annotations“. Hat er recht?
Lowkey nein, aber ich verstehe, woher er kommt! 😄
Real talk: Annotations sind modern und praktisch für viele Dinge. Aber:
- Enterprise-Projekte brauchen oft zentrale Konfiguration → web.xml
- Security-Constraints sind in XML übersichtlicher als in 50 Annotations verteilt
- Umgebungs-spezifische Configs (DEV/TEST/PROD) sind mit XML einfacher
- Legacy-Code den du maintainen musst → web.xml verstehen ist Pflicht!
Bernd’s Perspektive: Er arbeitet viel mit Spring Boot, wo vieles automatisch konfiguriert wird. In der „reinen“ Jakarta EE Welt ist web.xml aber immer noch sehr relevant.
Meine Empfehlung: Beides beherrschen! Hybrid-Ansatz ist King. 👑
📚 Quiz-Lösungen
Hier sind die Antworten zum Quiz von oben:
Frage 1: Was ist der Hauptzweck des Deployment Descriptors (web.xml)?
Antwort:
Der Deployment Descriptor (web.xml) ist die zentrale Konfigurationsdatei einer Java Web-Anwendung. Sie liegt im WEB-INF/ Ordner und beschreibt:
- Servlet-Definitionen und Mappings (welche Servlets unter welchen URLs erreichbar sind)
- Filter und Listener (Request-Verarbeitung und Event-Handling)
- Context-Parameter (globale Konfiguration für die gesamte Anwendung)
- Session-Konfiguration (Timeout, Cookie-Settings)
- Error-Pages (Custom-Error-Seiten für 404, 500, etc.)
- Security-Constraints (Zugriffskontrolle)
- Welcome-Files (Default-Seiten wie index.html)
Seit Servlet 3.0 können viele dieser Konfigurationen auch per Annotation direkt im Code definiert werden (@WebServlet, @WebFilter, etc.). Dennoch ist web.xml für zentrale, umgebungs-spezifische Konfiguration weiterhin wichtig.
Frage 2: Nenne zwei Szenarien, in denen web.xml besser ist als Annotations.
Antwort:
Szenario 1: Zentrale Übersicht Wenn du viele Servlets hast (z.B. 50 Stück), bietet web.xml eine zentrale Stelle, wo du ALLE URL-Mappings auf einen Blick siehst. Mit Annotations musst du jede Java-Datei einzeln öffnen, um zu sehen, welche URLs existieren.
Szenario 2: Umgebungs-spezifische Konfiguration Du willst unterschiedliche Configs für DEV, TEST und PROD (z.B. verschiedene Datenbank-URLs). Mit web.xml kannst du die Datei austauschen, ohne Code zu ändern oder neu zu kompilieren. Mit Annotations müsstest du den Code ändern und das Projekt neu bauen.
Weitere Szenarien:
- Legacy-Projekte, die
web.xmlverwenden - Framework-Integration (Spring, Struts erwarten oft
web.xmlKonfiguration) - Security-Constraints (übersichtlicher in XML als in vielen Annotations verteilt)
Frage 3: Erkläre den Unterschied zwischen Context-Parametern und Init-Parametern.
Antwort:
Context-Parameter:
- Scope: Global für die GESAMTE Web-Anwendung
- Definition: In
web.xmlmit<context-param> - Zugriff: Über
getServletContext().getInitParameter("param-name") - Use-Case: Globale Konfiguration (z.B. DB-URL, Admin-Email, Feature-Flags), die ALLE Servlets nutzen können
Beispiel:
<context-param>
<param-name>admin.email</param-name>
<param-value>admin@shop.com</param-value>
</context-param>
String email = getServletContext().getInitParameter("admin.email");
Init-Parameter:
- Scope: NUR für EIN spezifisches Servlet
- Definition: In
web.xmlinnerhalb von<servlet>mit<init-param>ODER per Annotation - Zugriff: Über
getInitParameter("param-name")(ohne ServletContext!) - Use-Case: Servlet-spezifische Konfiguration (z.B. Cache-Einstellung nur für ein bestimmtes Servlet)
Beispiel:
<servlet>
<servlet-name>ProductServlet</servlet-name>
<servlet-class>com.shop.ProductServlet</servlet-class>
<init-param>
<param-name>cache.enabled</param-name>
<param-value>true</param-value>
</init-param>
</servlet>
String cacheEnabled = getInitParameter("cache.enabled");
Merksatz:
- Context-Param = Global für ALLE
- Init-Param = Lokal für EINES
Frage 4: Was sind die drei Komponenten von MVC? Was ist die Aufgabe von jeder Komponente?
Antwort:
MVC = Model-View-Controller
1. Model (Daten & Business-Logik):
- Was: Java Beans (POJOs), DAO-Klassen, Service-Schichten
- Aufgaben:
- Geschäftsdaten speichern (Product, User, Order, etc.)
- Business-Logik ausführen (Berechnungen, Validierungen)
- Datenbankzugriff (über DAOs)
- KEINE: UI-Logik, HTTP-Requests verarbeiten, HTML generieren
- Beispiel:
Product.java(Bean),ProductDAO.java(DB-Zugriff)
2. View (Präsentations-Schicht):
- Was: JSPs, HTML-Templates
- Aufgaben:
- Daten anzeigen (vom Controller übergeben)
- UI rendern
- Formulare bereitstellen
- KEINE: Business-Logik, Datenbankzugriff, Request-Verarbeitung
- Beispiel:
product-list.jsp,product-detail.jsp
3. Controller (Steuerungs-Schicht):
- Was: Servlets
- Aufgaben:
- HTTP-Requests empfangen
- Request-Parameter auslesen
- Model aufrufen (DAOs, Services)
- View auswählen
- Forward zu JSP (mit Daten)
- KEINE: Business-Logik (gehört ins Model!), HTML generieren (gehört in View!)
- Beispiel:
ProductServlet.java
Flow:
User → Controller → Model → Controller → View → User
(Request) (Daten) (Daten) (Forward) (HTML)
Frage 5: Was ist der Hauptunterschied zwischen Model 1 und Model 2?
Antwort:
Model 1 (Legacy, NICHT empfohlen!):
User → JSP (direkt) → Java Beans → JSP → User
- Charakteristik: JSP macht ALLES (Request-Handling, Business-Logik, Präsentation)
- Problem: Java-Code (Scriptlets) in JSP → Spaghetti-Code
- Beispiel:
<% // Business-Logik in JSP! (SCHLECHT!) int id = Integer.parseInt(request.getParameter("id")); ProductDAO dao = new ProductDAO(); Product product = dao.getById(id); %> <h1><%= product.getName() %></h1>
Probleme:
- ❌ Business-Logik in View gemischt
- ❌ Nicht testbar
- ❌ Nicht wiederverwendbar
- ❌ Wartungs-Alptraum
- ❌ Designer können nicht arbeiten (Java-Code überall)
Model 2 (Modern, Best Practice!):
User → Servlet (Controller) → Java Beans (Model) → JSP (View) → User
- Charakteristik: Klare Trennung (Separation of Concerns!)
- Servlet = Controller (Request-Handling)
- Beans/DAO = Model (Business-Logik, Daten)
- JSP = View (nur Präsentation)
- Beispiel:
// Controller (Servlet) @WebServlet("/product") public class ProductServlet extends HttpServlet { protected void doGet(...) { Product product = productDAO.getById(id); // Model request.setAttribute("product", product); request.getRequestDispatcher("/view.jsp").forward(...); // View } }<!-- View (JSP) - nur Präsentation! --> <h1>${product.name}</h1>
Vorteile:
- ✅ Saubere Trennung
- ✅ Testbar
- ✅ Wiederverwendbar
- ✅ Wartbar
- ✅ Team kann parallel arbeiten
- ✅ Kein Java-Code in JSP!
Fazit: Model 2 ist die moderne, professionelle Art, Java Web-Anwendungen zu strukturieren!
Frage 6: Wann verwendest du Forward und wann Redirect?
Antwort:
Forward (Request-Dispatcher):
request.getRequestDispatcher("/WEB-INF/views/product.jsp")
.forward(request, response);
Eigenschaften:
- Server-seitig – Browser merkt nichts davon!
- URL bleibt gleich – im Browser wird die ursprüngliche URL angezeigt
- Request-Attribute bleiben erhalten –
request.setAttribute()Daten sind verfügbar - Gleicher Request – wird intern weitergeleitet
Use-Cases:
- ✅ Controller → View (MVC-Pattern!)
- ✅ Daten vom Servlet zur JSP übergeben
- ✅ Wenn URL nicht ändern soll
Redirect (sendRedirect):
response.sendRedirect("success.jsp");
Eigenschaften:
- Client-seitig – Browser macht einen NEUEN Request
- URL ändert sich – im Browser wird die neue URL angezeigt
- Request-Attribute gehen verloren – neuer Request = leere Attributes!
- Neuer Request – HTTP 302 Response an Browser, Browser macht neuen Request
Use-Cases:
- ✅ POST → GET (PRG-Pattern: Post-Redirect-Get)
- ✅ Nach Formular-Submit (verhindert doppelten Submit bei F5)
- ✅ Externe URLs (z.B.
response.sendRedirect("https://google.com")) - ✅ Wenn URL ändern soll
Vergleichstabelle:
| Szenario | Forward | Redirect |
|---|---|---|
| Controller → View | ✅ | ❌ |
| POST → GET (nach Form-Submit) | ❌ | ✅ |
Daten übergeben (request.setAttribute) | ✅ | ❌ |
| URL soll ändern | ❌ | ✅ |
| Externe URL | ❌ | ✅ |
| Browser soll nichts merken | ✅ | ❌ |
Merksatz:
- Daten zeigen? → Forward
- Aktion ausgeführt? → Redirect
Frage 7: Was ist das DAO-Pattern und warum ist es nützlich?
Antwort:
DAO = Data Access Object
Konzept: Kapsle ALLE Datenbankzugriffe in eigene Klassen (DAOs). Der Rest der Anwendung (Servlets, Services) kennt keine SQL-Details mehr!
Struktur:
// DAO-Interface (optional, aber empfohlen)
public interface ProductDAO {
List<Product> getAll();
Product getById(int id);
void save(Product product);
void delete(int id);
}
// Implementierung
public class JdbcProductDAO implements ProductDAO {
public List<Product> getAll() {
List<Product> products = new ArrayList<>();
try (Connection conn = DatabaseUtil.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM products")) {
while (rs.next()) {
products.add(mapRowToProduct(rs));
}
} catch (SQLException e) {
throw new RuntimeException("Error fetching products", e);
}
return products;
}
// Weitere Methoden...
}
Verwendung im Servlet:
@WebServlet("/products")
public class ProductServlet extends HttpServlet {
private ProductDAO productDAO = new JdbcProductDAO();
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// Einfach DAO aufrufen - kein SQL im Servlet!
List<Product> products = productDAO.getAll();
request.setAttribute("products", products);
request.getRequestDispatcher("/WEB-INF/views/products.jsp")
.forward(request, response);
}
}
Vorteile:
- Separation of Concerns:
- Controller kennt keine DB-Details
- Nur DAO weiß über SQL Bescheid
- Wiederverwendbarkeit:
- Mehrere Servlets können dasselbe DAO nutzen
- Kein doppelter Code!
- Austauschbarkeit:
- MySQL → PostgreSQL? Nur DAO ändern!
- JDBC → JPA? Neue DAO-Implementierung schreiben!
- Rest der App bleibt unverändert
- Testbarkeit:
- Mock-DAO für Unit-Tests erstellen
- Controller isoliert testen (ohne echte DB)
- Wartbarkeit:
- Alle DB-Queries an einer Stelle
- Änderungen nur im DAO nötig
Ohne DAO (schlecht!):
// Controller mit DB-Code vermischt! (NICHT machen!)
@WebServlet("/products")
public class ProductServlet extends HttpServlet {
protected void doGet(...) {
// SQL direkt im Servlet! (SCHLECHT!)
Connection conn = DriverManager.getConnection(...);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM products");
// ...
}
}
Mit DAO (gut!):
// Sauber getrennt!
@WebServlet("/products")
public class ProductServlet extends HttpServlet {
private ProductDAO dao = new ProductDAO();
protected void doGet(...) {
List<Product> products = dao.getAll(); // Einfach!
// ...
}
}
Fazit: DAO-Pattern ist ein Muss für professionelle Java Web-Anwendungen!
Frage 8: Warum sollten JSPs im WEB-INF Ordner liegen?
Antwort:
Grund: Dateien in WEB-INF/ sind vom Browser NICHT direkt erreichbar!
Was bedeutet das?
Ohne WEB-INF (schlecht!):
webapp/
├── product.jsp ← Direkt erreichbar!
└── css/
└── style.css
http://localhost:8080/shop/product.jsp
→ Funktioniert! User kann JSP direkt aufrufen, ohne über Controller zu gehen!
Problem:
- ❌ MVC wird umgangen (Controller wird ignoriert)
- ❌ Keine Validierung möglich
- ❌ Keine Authentifizierung möglich
- ❌ JSP erwartet Daten vom Controller – sind aber nicht da → Fehler!
- ❌ Security-Risk (User kann alle JSPs direkt aufrufen)
Mit WEB-INF (gut!):
webapp/
├── WEB-INF/
│ └── views/
│ └── product.jsp ← Geschützt!
└── css/
└── style.css
http://localhost:8080/shop/WEB-INF/views/product.jsp
→ 404 Error! Browser kann nicht direkt auf WEB-INF zugreifen!
Einziger Weg zur JSP:
// Im Servlet (Controller):
request.getRequestDispatcher("/WEB-INF/views/product.jsp")
.forward(request, response);
Vorteile:
- Erzwingt MVC:
- User MUSS über Servlet gehen
- Controller hat volle Kontrolle
- Security:
- Keine direkten JSP-Aufrufe möglich
- Nur authentifizierte/autorisierte Requests (wenn Controller prüft)
- Validierung:
- Controller kann Input validieren, bevor View gezeigt wird
- Konsistenz:
- Controller setzt immer benötigte Daten
- JSPs crashen nicht wegen fehlender Attribute
- Best Practice:
- Jedes professionelle Java Web-Projekt macht es so!
Struktur-Empfehlung:
webapp/
├── WEB-INF/
│ ├── views/ ← JSPs (geschützt!)
│ │ ├── product-list.jsp
│ │ ├── product-detail.jsp
│ │ └── error.jsp
│ ├── web.xml ← Config
│ └── lib/ ← JARs
├── css/ ← Öffentlich
│ └── style.css
├── js/ ← Öffentlich
│ └── script.js
└── images/ ← Öffentlich
└── logo.png
Regel:
- JSPs → Immer in
WEB-INF/ - Statische Ressourcen (CSS, JS, Bilder) → Außerhalb von
WEB-INF/(müssen direkt erreichbar sein!)
🎉 Tag 4 geschafft!
Slay! Du hast es geschafft! 🚀
Real talk: Architektur ist nicht so sexy wie Code schreiben. Aber es ist das Fundament für alles, was kommt.
Das hast du heute gelernt:
- ✅ Was der Deployment Descriptor (web.xml) ist
- ✅ Unterschied zwischen Annotations und XML-Konfiguration
- ✅ Wann du was verwenden solltest
- ✅ Was MVC (Model-View-Controller) bedeutet
- ✅ Unterschied zwischen Model 1 und Model 2
- ✅ Warum Model 2 besser ist (Separation of Concerns!)
- ✅ Forward vs. Redirect
- ✅ DAO-Pattern
- ✅ Best Practices für saubere Webarchitekturen
Du kannst jetzt:
- web.xml schreiben und verstehen
- Saubere MVC-Architekturen designen
- Spaghetti-Code vermeiden
- Request-Dispatcher richtig einsetzen
- Model, View und Controller trennen
Honestly? Das ist HUGE! Viele Entwickler springen direkt zu Spring Boot und verstehen nie, was unter der Haube passiert. Du hast jetzt das Fundament. Das ist Gold wert für deine Karriere.
🔮 Wie geht’s weiter?
Morgen (Tag 5): JSP & Expression Languages (Teil 1)
Was dich erwartet:
- Was JSPs sind und wie sie funktionieren
- JSP-Lifecycle verstehen
- Expression Language (EL) Syntax
- Implizite Objekte in JSPs
- JSP-Direktiven
- Actions vs. Scriptlets
- Warum EL besser ist als Scriptlets – das wird dein Game-Changer für sauberen View-Code! 🔥
Brauchst du eine Pause?
Mach sie! MVC-Konzepte brauchen Zeit zum Verdauen.
Tipp für heute Abend:
Baue die Blog-Challenge aus. Füge Features hinzu:
- Kommentare zu Posts
- Kategorien
- Suche
Learning by doing! 🔧
🔧 Troubleshooting
Problem: JSP im WEB-INF wird nicht gefunden
Lösung:
// FALSCH:
request.getRequestDispatcher("WEB-INF/views/product.jsp")
// RICHTIG:
request.getRequestDispatcher("/WEB-INF/views/product.jsp")
Führender Slash / ist wichtig!
Problem: Attribute kommt nicht in JSP an
Lösung:
// Prüfe Scope:
request.setAttribute("product", product); // Request-Scope
session.setAttribute("user", user); // Session-Scope
// In JSP:
${product} // aus Request-Scope
${user} // aus Session-Scope
Problem: Forward funktioniert nicht
Lösung:
- Prüfe ob Response bereits committed ist
- Forward muss VOR
response.getWriter()oderresponse.sendRedirect()passieren - Nur EIN Forward pro Request!
Problem: web.xml wird nicht geladen
Lösung:
- Prüfe Pfad: Muss in
WEB-INF/web.xmlliegen! - Prüfe XML-Syntax (gut formatiert?)
- Prüfe Namespace und Version (Jakarta EE 10+:
jakarta.*) - Server-Log prüfen (Parsing-Fehler?)
📚 Resources & Links
MVC & Model 2:
- Jakarta EE Tutorial: https://jakarta.ee/learn/tutorials/
- MVC Pattern Explained: https://www.baeldung.com/mvc-servlet-jsp
web.xml:
- Jakarta Servlet Spec: https://jakarta.ee/specifications/servlet/
- web.xml Reference: https://docs.oracle.com/cd/E13222_01/wls/docs92/webapp/web_xml.html
Best Practices:
- Java EE Blueprints: https://www.oracle.com/java/technologies/java-blueprints.html
- Design Patterns: Gang of Four (GoF) Book
💬 Feedback?
War Tag 4 zu theoretisch? Zu abstrakt? Mehr Code-Beispiele gewünscht?
Schreib uns: feedback@java-developer.online
Wir wollen, dass du erfolgreich lernst!
Bis morgen! 👋
Elyndra
elyndra@java-developer.online
Senior Developer bei Java Fleet Systems Consulting
Java Web Basic – Tag 4 von 10
Teil der Java Fleet Learning-Serie
© 2025 Java Fleet Systems Consulting

