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.
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
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.
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()); } }
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("}"); }
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
Mechanism | Opis | Zalety | Wady |
---|---|---|---|
Cookies | JSESSIONID w cookie | Automatyczne, przezroczyste | Można wyłączyć w przeglądarce |
URL Rewriting | Session ID w URL | Działa bez cookies | Brzydkie URL, trudne w utrzymaniu |
Hidden Fields | Session ID w formach | Działa zawsze | Tylko 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); } }
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"); } }
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.
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).
Użyj @MultipartConfig na servlecie i getPart() method z request. Pamiętaj o ustawieniu enctype=”multipart/form-data” w formularzu HTML.
ServletContext to object reprezentujący całą web application. Pozwala na sharing danych między servletami, dostęp do init parameters aplikacji i resource files.
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:
- LoginServlet: obsługuje POST, sprawdza credentials, tworzy session
- DashboardServlet: wyświetla dane użytkownika (wymaga sesji)
- LogoutServlet: niszczy sesję, przekierowuje do login
- AuthFilter: sprawdza czy user jest zalogowany dla protected URLs
- 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?