Servlet API – podstawy Java web

TL;DR: Servlet API to fundament Java web development – pozwala tworzyć aplikacje webowe które obsługują HTTP requests i generują responses. Servlety to Java klasy które dziedziczą z HttpServlet i implementują metody doGet(), doPost() dla różnych typów żądań HTTP.

Dlaczego Servlet API to fundament Java web?

Servlet API to podstawa całego Java web stack – bez niego nie byłoby Spring MVC, JSF ani żadnych innych frameworków web. To jak nauka jazdy na zwykłym samochodzie przed przesiadką na automatyczną skrzynię. Zrozumienie servletów daje solidne fundamenty do pracy z dowolnym Java web framework.

Servlet API 3.0 (Java EE 6) wprowadził adnotacje które znacznie upraszczają konfigurację w porównaniu do XML. Spring Boot pod spodem używa embedded servlet container (Tomcat).

Co się nauczysz:

  • Czym są servlety i jak działają w kontenerze web
  • Lifecycle servletu: init(), service(), destroy()
  • Obsługa HTTP requests: GET, POST, PUT, DELETE
  • Praca z HttpServletRequest i HttpServletResponse
  • Session management i cookies w servletach
Wymagania wstępne: Podstawy Java (klasy, dziedziczenie), znajomość HTTP protocol, pojęcie client-server architecture, podstawy HTML.

Czym jest Servlet?

Servlet to Java klasa która obsługuje żądania HTTP od klientów (przeglądarek) i generuje odpowiedzi. Servlety działają w servlet container (jak Tomcat, Jetty) który zarządza ich lifecycle i dostarcza środowisko wykonania.

Servlet Container – środowisko wykonania dla servletów które zarządza ich lifecycle, mapowaniem URL, bezpieczeństwem i komunikacją HTTP.
Servlet to jak kelner w restauracji – otrzymuje zamówienie (HTTP request), przetwarza je w kuchni (business logic) i przynosi odpowiedź (HTTP response). Container to jak manager restauracji który zarządza kelnerami.

Podstawowa struktura servletu

// HelloServlet.java - Podstawowy servlet
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/hello")  // Mapowanie URL - Servlet 3.0+
public class HelloServlet extends HttpServlet {
    
    @Override
    protected void doGet(HttpServletRequest request, 
                        HttpServletResponse response) 
            throws ServletException, IOException {
        
        // Ustawienie typu odpowiedzi
        response.setContentType("text/html;charset=UTF-8");
        
        // Pobranie parametru z URL
        String name = request.getParameter("name");
        if (name == null) {
            name = "World";
        }
        
        // Generowanie odpowiedzi HTML
        PrintWriter out = response.getWriter();
        out.println("");
        out.println("

Hello " + name + "!

"); out.println("

Current time: " + new java.util.Date() + "

"); out.println(""); } }

Servlet Lifecycle – cykl życia

Container zarządza całym lifecycle servletu:

1. Loading i Instantiation

Container ładuje klasę servletu i tworzy instancję (zwykle przy pierwszym żądaniu).

2. Initialization – init()

public class DatabaseServlet extends HttpServlet {
    private DataSource dataSource;
    
    @Override
    public void init() throws ServletException {
        // Wykonywane raz przy inicjalizacji
        try {
            // Konfiguracja połączenia z bazą danych
            Context initCtx = new InitialContext();
            dataSource = (DataSource) initCtx.lookup("java:comp/env/jdbc/mydb");
            
            System.out.println("Servlet initialized: " + new Date());
        } catch (NamingException e) {
            throw new ServletException("Database initialization failed", e);
        }
    }
}

3. Service – request handling

Metoda service() wywołuje odpowiednie metody (doGet, doPost, etc.) dla każdego żądania:

@WebServlet("/users")
public class UserServlet extends HttpServlet {
    
    @Override
    protected void doGet(HttpServletRequest request, 
                        HttpServletResponse response) 
            throws ServletException, IOException {
        // Obsługa GET - pobieranie danych
        String userId = request.getParameter("id");
        User user = userService.findById(userId);
        
        request.setAttribute("user", user);
        RequestDispatcher dispatcher = request.getRequestDispatcher("/user.jsp");
        dispatcher.forward(request, response);
    }
    
    @Override
    protected void doPost(HttpServletRequest request, 
                         HttpServletResponse response) 
            throws ServletException, IOException {
        // Obsługa POST - tworzenie/aktualizacja danych
        String name = request.getParameter("name");
        String email = request.getParameter("email");
        
        User user = new User(name, email);
        userService.save(user);
        
        // Redirect po POST (PRG pattern)
        response.sendRedirect("/users?id=" + user.getId());
    }
}

4. Destroy – cleanup

@Override
public void destroy() {
    // Cleanup zasobów przed zniszczeniem servletu
    if (dataSource != null) {
        // Zamknij połączenia, zwolnij zasoby
        System.out.println("Servlet destroyed: " + new Date());
    }
}
Pro tip: init() i destroy() są wywoływane tylko raz w lifecycle servletu. service() methods są wywoływane dla każdego żądania i muszą być thread-safe.

HTTP Request i Response handling

HttpServletRequest – dostęp do danych żądania

@Override
protected void doPost(HttpServletRequest request, 
                     HttpServletResponse response) 
        throws ServletException, IOException {
    
    // Parametry z URL i form data
    String username = request.getParameter("username");
    String[] hobbies = request.getParameterValues("hobbies");
    
    // Headers
    String userAgent = request.getHeader("User-Agent");
    String acceptLanguage = request.getHeader("Accept-Language");
    
    // Request info
    String method = request.getMethod();        // GET, POST, etc.
    String contextPath = request.getContextPath(); // /myapp
    String servletPath = request.getServletPath(); // /users
    String remoteAddr = request.getRemoteAddr();   // Client IP
    
    // Session
    HttpSession session = request.getSession();
    session.setAttribute("username", username);
    
    // Request attributes (for forwarding to JSP)
    request.setAttribute("message", "Welcome " + username);
}

HttpServletResponse – generowanie odpowiedzi

// JSON response example
@Override
protected void doGet(HttpServletRequest request, 
                    HttpServletResponse response) 
        throws ServletException, IOException {
    
    // Ustawienie response headers
    response.setContentType("application/json;charset=UTF-8");
    response.setHeader("Cache-Control", "no-cache");
    response.setStatus(HttpServletResponse.SC_OK); // 200
    
    // Cookies
    Cookie userCookie = new Cookie("lastVisit", String.valueOf(System.currentTimeMillis()));
    userCookie.setMaxAge(60 * 60 * 24); // 24 hours
    response.addCookie(userCookie);
    
    // JSON response body
    PrintWriter out = response.getWriter();
    out.println("{");
    out.println("  \"status\": \"success\",");
    out.println("  \"message\": \"Data retrieved successfully\",");
    out.println("  \"timestamp\": " + System.currentTimeMillis());
    out.println("}");
}
Pułapka: PrintWriter z response.getWriter() jest buffered. Użyj out.flush() lub response.flushBuffer() aby wymusić wysłanie danych do klienta natychmiast.

Session Management

Praca z HttpSession

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    
    @Override
    protected void doPost(HttpServletRequest request, 
                         HttpServletResponse response) 
            throws ServletException, IOException {
        
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        
        if (authenticate(username, password)) {
            // Tworzenie nowej sesji lub pobranie istniejącej
            HttpSession session = request.getSession(true);
            
            // Przechowywanie danych w sesji
            session.setAttribute("username", username);
            session.setAttribute("loginTime", new Date());
            session.setMaxInactiveInterval(30 * 60); // 30 minut
            
            // Bezpieczeństwo - regeneruj session ID po logowaniu
            request.changeSessionId();
            
            response.sendRedirect("/dashboard");
        } else {
            request.setAttribute("error", "Invalid credentials");
            RequestDispatcher dispatcher = request.getRequestDispatcher("/login.jsp");
            dispatcher.forward(request, response);
        }
    }
}

@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
    
    @Override
    protected void doPost(HttpServletRequest request, 
                         HttpServletResponse response) 
            throws ServletException, IOException {
        
        HttpSession session = request.getSession(false);
        if (session != null) {
            session.invalidate(); // Usuwa sesję
        }
        
        response.sendRedirect("/login");
    }
}

Session tracking mechanisms

MechanismOpisZaletyWady
CookiesJSESSIONID w cookieAutomatyczne, przezroczysteMożna wyłączyć w przeglądarce
URL RewritingSession ID w URLDziała bez cookiesBrzydkie URL, trudne w utrzymaniu
Hidden FieldsSession ID w formachDziała zawszeTylko dla POST requests

Konfiguracja – web.xml vs adnotacje

Tradycyjna konfiguracja XML (Servlet 2.5 i wcześniej)




    
    
        UserServlet
        com.example.UserServlet
        
            maxUsers
            1000
        
    
    
    
        UserServlet
        /users/*
    
    
    
        30
    

Nowoczesna konfiguracja adnotacjami (Servlet 3.0+)

@WebServlet(
    name = "UserServlet",
    urlPatterns = {"/users", "/users/*"},
    initParams = {
        @WebInitParam(name = "maxUsers", value = "1000")
    },
    loadOnStartup = 1  // Ładuj przy starcie aplikacji
)
public class UserServlet extends HttpServlet {
    
    private int maxUsers;
    
    @Override
    public void init() throws ServletException {
        String maxUsersParam = getInitParameter("maxUsers");
        maxUsers = Integer.parseInt(maxUsersParam);
    }
}
Pro tip: Adnotacje są wygodniejsze dla prostych konfiguracji, ale web.xml daje więcej kontroli i możliwość zmiany konfiguracji bez rekompilacji.

Filtrowanie i preprocessing

Servlet Filters

@WebFilter("/*")  // Filtruj wszystkie requests
public class EncodingFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, 
                        ServletResponse response, 
                        FilterChain chain) 
            throws IOException, ServletException {
        
        // Pre-processing
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        
        // Log request
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        System.out.println("Request: " + httpRequest.getMethod() + 
                          " " + httpRequest.getRequestURI());
        
        // Przekaż żądanie dalej w łańcuchu
        chain.doFilter(request, response);
        
        // Post-processing
        System.out.println("Response sent");
    }
}

Czy servlety są thread-safe?

Nie! Container tworzy jedną instancję servletu dla wszystkich żądań. Musisz unikać instance variables lub synchronizować dostęp. Używaj local variables w metodach doGet/doPost.

Kiedy używać forward() a kiedy sendRedirect()?

forward() przekazuje żądanie wewnętrznie (URL się nie zmienia, dzielisz dane przez request attributes). sendRedirect() wysyła nowy request do przeglądarki (URL się zmienia, używaj po POST operations).

Jak obsłużyć file upload w servletach?

Użyj @MultipartConfig na servlecie i getPart() method z request. Pamiętaj o ustawieniu enctype=”multipart/form-data” w formularzu HTML.

Co to jest servlet context?

ServletContext to object reprezentujący całą web application. Pozwala na sharing danych między servletami, dostęp do init parameters aplikacji i resource files.

Czy mogę użyć servletów z Spring?

Tak! Spring MVC jest built on top of Servlet API. DispatcherServlet to główny servlet Spring MVC. Możesz też mieszać raw servlety z Spring controllers w jednej aplikacji.

🚀 Zadanie dla Ciebie

Stwórz prostą aplikację servlet z następującymi funkcjami:

  1. LoginServlet: obsługuje POST, sprawdza credentials, tworzy session
  2. DashboardServlet: wyświetla dane użytkownika (wymaga sesji)
  3. LogoutServlet: niszczy sesję, przekierowuje do login
  4. AuthFilter: sprawdza czy user jest zalogowany dla protected URLs
  5. Bonus: dodaj counter odwiedzin używając ServletContext

Deploy na Tomcat 8 i przetestuj pełny flow: login → dashboard → logout.

Przydatne zasoby:

Czy używasz jeszcze pure servletów czy przeszedłeś już na frameworki jak Spring MVC? Jakie są największe zalety i wady raw servletów w twoim doświadczeniu?

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Przewijanie do góry