Java Web Basic – Tag 4 von 10
Von Elyndra Valen, Senior Developer bei Java Fleet Systems Consulting


🗺️ Deine Position im Kurs

TagThemaStatus
1Java EE Überblick & HTTP ✅ Abgeschlossen
2HTTP-Protokoll Vertiefung & Zustandslosigkeit✅ Abgeschlossen
3Servlets & Servlet API✅ Abgeschlossen
→ 4Deployment Descriptor & MVC vs Model 2👉 DU BIST HIER!
5JSP & Expression Languages (Teil 1)🔜 Kommt als nächstes
6Java Beans, Actions, Scopes & Direktiven🔒 Noch nicht freigeschaltet
7Include-Action vs Include-Direktive🔒 Noch nicht freigeschaltet
8JSTL – Java Standard Tag Libraries🔒 Noch nicht freigeschaltet
9Java Web und Datenbanken – Datasource🔒 Noch nicht freigeschaltet
10Connection 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:

  1. Deployment Descriptor (web.xml) – Die zentrale Konfigurationsdatei
  2. 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?

  1. Servlet Definition (<servlet>):
    • Gibt dem Servlet einen internen Namen: HelloServlet
    • Definiert die Java-Klasse: com.example.HelloServlet
  2. Servlet Mapping (<servlet-mapping>):
    • Verknüpft den internen Namen mit einem URL-Pattern
    • URL /hello → HelloServlet

Das Problem:

Du musstest für JEDES Servlet:

  1. Die Java-Klasse schreiben
  2. In web.xml das Servlet definieren
  3. In web.xml das 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:

  1. index.html
  2. index.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:

  1. User versucht /admin/dashboard aufzurufen
  2. Server prüft: Ist User eingeloggt?
  3. Wenn nein → Redirect zu /login.jsp
  4. Nach Login → Prüfung der Rolle
  5. Wenn admin → Zugriff erlaubt
  6. 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-Param
  • getServletContext().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:

  1. Model:
    • Enthält Business-Logik
    • Daten-Zugriff (DAO)
    • Java Beans / POJOs
    • KEINE UI-Logik!
  2. View:
    • Präsentiert Daten
    • JSPs / HTML
    • KEINE Business-Logik!
  3. 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:

  1. User ruft JSP direkt auf
  2. JSP enthält Business-Logik (Java-Code!)
  3. JSP ruft Beans auf
  4. 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:

  1. User ruft Servlet auf (Controller)
  2. Servlet verarbeitet Request
  3. Servlet ruft Model (Beans, DAO) auf
  4. Servlet setzt Daten in Request-Attribute
  5. Servlet forwarded zu JSP (View)
  6. 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?

SzenarioForwardRedirect
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:

  1. Model:
    • BlogPost Bean (id, title, content, author, date)
    • BlogPostDAO für Datenzugriff (In-Memory-Liste reicht)
  2. Controller:
    • BlogServlet mit URL /blog
    • Bei /blog → Alle Posts anzeigen
    • Bei /blog?id=X → Einen Post anzeigen
  3. View:
    • blog-list.jsp im WEB-INF/views/ Ordner
    • blog-post.jsp im WEB-INF/views/ Ordner
  4. 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-INF Ordner (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:

  1. Enterprise-Projekte brauchen oft zentrale Konfiguration → web.xml
  2. Security-Constraints sind in XML übersichtlicher als in 50 Annotations verteilt
  3. Umgebungs-spezifische Configs (DEV/TEST/PROD) sind mit XML einfacher
  4. 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.xml verwenden
  • Framework-Integration (Spring, Struts erwarten oft web.xml Konfiguration)
  • 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.xml mit <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.xml innerhalb 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.jspproduct-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:

SzenarioForwardRedirect
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:

  1. Separation of Concerns:
    • Controller kennt keine DB-Details
    • Nur DAO weiß über SQL Bescheid
  2. Wiederverwendbarkeit:
    • Mehrere Servlets können dasselbe DAO nutzen
    • Kein doppelter Code!
  3. Austauschbarkeit:
    • MySQL → PostgreSQL? Nur DAO ändern!
    • JDBC → JPA? Neue DAO-Implementierung schreiben!
    • Rest der App bleibt unverändert
  4. Testbarkeit:
    • Mock-DAO für Unit-Tests erstellen
    • Controller isoliert testen (ohne echte DB)
  5. 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:

  1. Erzwingt MVC:
    • User MUSS über Servlet gehen
    • Controller hat volle Kontrolle
  2. Security:
    • Keine direkten JSP-Aufrufe möglich
    • Nur authentifizierte/autorisierte Requests (wenn Controller prüft)
  3. Validierung:
    • Controller kann Input validieren, bevor View gezeigt wird
  4. Konsistenz:
    • Controller setzt immer benötigte Daten
    • JSPs crashen nicht wegen fehlender Attribute
  5. 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() oder response.sendRedirect() passieren
  • Nur EIN Forward pro Request!

Problem: web.xml wird nicht geladen

Lösung:

  1. Prüfe Pfad: Muss in WEB-INF/web.xml liegen!
  2. Prüfe XML-Syntax (gut formatiert?)
  3. Prüfe Namespace und Version (Jakarta EE 10+: jakarta.*)
  4. Server-Log prüfen (Parsing-Fehler?)

MVC & Model 2:

web.xml:

Best Practices:


💬 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