Ir al contenido principal

Servlets y JSP básicos

Introducción

Los Servlets y JSP (JavaServer Pages) son tecnologías fundamentales para el desarrollo web con Java. Estas herramientas nos permiten crear aplicaciones web dinámicas, procesando solicitudes del cliente y generando respuestas personalizadas. Mientras que los Servlets son clases Java especializadas en manejar peticiones HTTP, los JSP complementan esta funcionalidad permitiendo la creación de contenido dinámico mediante una sintaxis más cercana al HTML. En este artículo, aprenderemos los fundamentos de estas tecnologías y cómo combinarlas para crear aplicaciones web básicas con Java.

Entendiendo los Servlets

¿Qué es un Servlet?

Un Servlet es una clase Java que extiende las capacidades de un servidor web para generar contenido dinámico. Es el equivalente Java a otras tecnologías de servidor como PHP o ASP.NET.

Los Servlets:

  • Procesan peticiones HTTP (GET, POST, etc.)
  • Generan respuestas dinámicas
  • Mantienen el estado entre peticiones
  • Se ejecutan en un contenedor de Servlets (como Tomcat o Jetty)

Configuración del entorno para Servlets

Para desarrollar con Servlets necesitamos:

  1. Un servidor compatible con Servlets (Tomcat, Jetty, GlassFish, etc.)
  2. Las dependencias de la API de Servlet (Jakarta EE o Java EE)

Si estamos utilizando Maven, podemos añadir la dependencia:

<dependency>
    <groupId>jakarta.servlet</groupId>
    <artifactId>jakarta.servlet-api</artifactId>
    <version>5.0.0</version>
    <scope>provided</scope>
</dependency>

Estructura de un proyecto web Java

Un proyecto web Java típico tiene la siguiente estructura:

webapp/
├── WEB-INF/
│   ├── web.xml           // Descriptor de despliegue
│   ├── classes/          // Clases compiladas (.class)
│   └── lib/              // Bibliotecas JAR
├── index.html            // Páginas estáticas
└── recursos/             // Imágenes, CSS, JavaScript, etc.

Creación de un Servlet básico

Para crear un Servlet, debemos:

  1. Crear una clase que extienda HttpServlet
  2. Sobrescribir los métodos doGet, doPost, etc., según las peticiones que queramos manejar
  3. Configurar el Servlet en el descriptor de despliegue (web.xml) o con anotaciones

Veamos un ejemplo básico:

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/hola")
public class HolaMundoServlet extends HttpServlet {
    
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        // Establecer el tipo de contenido de la respuesta
        response.setContentType("text/html");
        response.setCharacterEncoding("UTF-8");
        
        // Obtener el writer para escribir la respuesta
        PrintWriter out = response.getWriter();
        
        // Escribir el contenido HTML
        out.println("<!DOCTYPE html>");
        out.println("<html>");
        out.println("<head>");
        out.println("<title>Hola Mundo Servlet</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>¡Hola desde mi primer Servlet!</h1>");
        out.println("<p>La hora actual es: " + new java.util.Date() + "</p>");
        out.println("</body>");
        out.println("</html>");
    }
}

La anotación @WebServlet("/hola") indica que este Servlet responderá a peticiones en la ruta /hola.

Ciclo de vida de un Servlet

Los Servlets siguen un ciclo de vida gestionado por el contenedor:

  1. Carga: El contenedor carga la clase del Servlet
  2. Inicialización: Se llama al método init() una sola vez
  3. Servicio: El método service() procesa las peticiones, delegando en doGet(), doPost(), etc.
  4. Destrucción: Se llama al método destroy() cuando se detiene el Servlet

Este ciclo nos permite realizar tareas de inicialización y limpieza:

@Override
public void init() throws ServletException {
    // Código de inicialización (conexiones a BD, cargar configuración, etc.)
    System.out.println("Servlet inicializado");
}

@Override
public void destroy() {
    // Código de limpieza (cerrar conexiones, liberar recursos, etc.)
    System.out.println("Servlet destruido");
}

Procesamiento de formularios

Los Servlets pueden procesar datos enviados mediante formularios HTML:

@WebServlet("/procesar-formulario")
public class FormularioServlet extends HttpServlet {
    
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        // Obtener parámetros del formulario
        String nombre = request.getParameter("nombre");
        String email = request.getParameter("email");
        
        // Establecer tipo de contenido
        response.setContentType("text/html");
        response.setCharacterEncoding("UTF-8");
        
        // Generar respuesta
        PrintWriter out = response.getWriter();
        out.println("<!DOCTYPE html>");
        out.println("<html>");
        out.println("<head><title>Datos recibidos</title></head>");
        out.println("<body>");
        out.println("<h1>Datos recibidos</h1>");
        out.println("<p>Nombre: " + nombre + "</p>");
        out.println("<p>Email: " + email + "</p>");
        out.println("</body></html>");
    }
}

El formulario HTML correspondiente:

<form action="procesar-formulario" method="post">
    <label for="nombre">Nombre:</label>
    <input type="text" id="nombre" name="nombre" required><br>
    
    <label for="email">Email:</label>
    <input type="email" id="email" name="email" required><br>
    
    <button type="submit">Enviar</button>
</form>

Introducción a JavaServer Pages (JSP)

¿Qué es JSP?

JSP (JavaServer Pages) es una tecnología que permite crear páginas web dinámicas usando código Java incrustado en HTML. Cada archivo JSP se compila automáticamente en un Servlet la primera vez que se accede a él.

Las ventajas de JSP incluyen:

  • Separación más clara entre presentación y lógica
  • Sintaxis más amigable para los diseñadores web
  • Compilación automática

Estructura básica de un JSP

Un archivo JSP tiene extensión .jsp y puede contener:

  • HTML estático
  • Elementos JSP (directivas, declaraciones, scriptlets, expresiones)
  • Etiquetas personalizadas y EL (Expression Language)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
    <title>Mi primera página JSP</title>
</head>
<body>
    <h1>¡Hola desde JSP!</h1>
    <p>La fecha actual es: <%= new java.util.Date() %></p>
    
    <%
        // Esto es un scriptlet (código Java)
        for (int i = 1; i <= 5; i++) {
            out.println("<p>Número " + i + "</p>");
        }
    %>
</body>
</html>

Elementos JSP principales

Los elementos JSP más importantes son:

  1. Directivas: Configuran la página JSP

    <%@ page import="java.util.*" %>
    <%@ include file="cabecera.jsp" %>
    
  2. Declaraciones: Definen variables y métodos

    <%! 
        private int contador = 0;
        private String getSaludo() {
            return "¡Hola visitante!";
        }
    %>
    
  3. Scriptlets: Bloques de código Java

    <% 
        String nombre = request.getParameter("nombre");
        if (nombre != null && !nombre.isEmpty()) {
            out.println("Bienvenido, " + nombre);
        }
    %>
    
  4. Expresiones: Evaluación y salida de valores

    <p>La hora es: <%= new java.util.Date() %></p>
    <p>Contador: <%= ++contador %></p>
    

Objetos implícitos en JSP

JSP proporciona varios objetos predefinidos (implícitos) que podemos usar directamente:

  • request: El objeto HttpServletRequest
  • response: El objeto HttpServletResponse
  • session: El objeto HttpSession
  • application: El contexto de la aplicación (ServletContext)
  • out: El objeto JspWriter para la salida
  • page: Referencia al Servlet generado (this)
  • pageContext: Contexto de la página JSP
  • config: Configuración del Servlet
  • exception: Objeto Exception (solo en páginas de error)
<p>Tu dirección IP es: <%= request.getRemoteAddr() %></p>
<p>Nombre de servidor: <%= request.getServerName() %></p>

<% 
    // Almacenando datos en la sesión
    session.setAttribute("usuario", "Juan");
%>

Acciones JSP estándar

Las acciones JSP son etiquetas que ejecutan acciones en tiempo de ejecución:

<!-- Incluir contenido de otro JSP -->
<jsp:include page="menu.jsp" />

<!-- Reenviar la petición a otro recurso -->
<jsp:forward page="resultado.jsp" />

<!-- Crear un JavaBean -->
<jsp:useBean id="usuario" class="es.miempresa.modelo.Usuario" />

<!-- Establecer propiedades del bean -->
<jsp:setProperty name="usuario" property="nombre" value="Ana" />

<!-- Obtener propiedades del bean -->
Nombre: <jsp:getProperty name="usuario" property="nombre" />

Expression Language (EL)

El Lenguaje de Expresiones (EL) proporciona una forma concisa de acceder a datos:

<!-- Acceder a parámetros, atributos y beans -->
<p>Nombre: ${param.nombre}</p>
<p>Usuario: ${sessionScope.usuario}</p>
<p>Email: ${usuario.email}</p>

<!-- Operadores -->
<p>${10 + 5}</p>
<p>${empty param.busqueda ? "No hay búsqueda" : "Buscando: ".concat(param.busqueda)}</p>

Integración de Servlets y JSP: Patrón MVC

El patrón Modelo-Vista-Controlador (MVC)

La combinación de Servlets y JSP nos permite implementar el patrón MVC:

  • Modelo: Clases Java (JavaBeans) que representan los datos
  • Vista: Páginas JSP que presentan la información
  • Controlador: Servlets que procesan peticiones y coordinan el flujo

Implementación básica del patrón MVC

  1. Modelo (JavaBean):
package es.miempresa.modelo;

public class Producto {
    private int id;
    private String nombre;
    private double precio;
    
    // Constructor, getters y setters
    public Producto(int id, String nombre, double precio) {
        this.id = id;
        this.nombre = nombre;
        this.precio = precio;
    }
    
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    
    public String getNombre() { return nombre; }
    public void setNombre(String nombre) { this.nombre = nombre; }
    
    public double getPrecio() { return precio; }
    public void setPrecio(double precio) { this.precio = precio; }
}
  1. Controlador (Servlet):
@WebServlet("/productos")
public class ProductosServlet extends HttpServlet {
    
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        // Simular obtención de datos (en un caso real, vendría de una BD)
        List<Producto> productos = new ArrayList<>();
        productos.add(new Producto(1, "Portátil", 899.99));
        productos.add(new Producto(2, "Smartphone", 499.99));
        productos.add(new Producto(3, "Tablet", 299.99));
        
        // Almacenar datos en el request
        request.setAttribute("listaProductos", productos);
        
        // Reenviar a la vista JSP
        RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/vistas/productos.jsp");
        dispatcher.forward(request, response);
    }
}
  1. Vista (JSP):
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
    <title>Listado de Productos</title>
    <style>
        table { border-collapse: collapse; width: 100%; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        th { background-color: #f2f2f2; }
    </style>
</head>
<body>
    <h1>Catálogo de Productos</h1>
    
    <table>
        <tr>
            <th>ID</th>
            <th>Nombre</th>
            <th>Precio</th>
        </tr>
        <c:forEach var="producto" items="${listaProductos}">
            <tr>
                <td>${producto.id}</td>
                <td>${producto.nombre}</td>
                <td>${producto.precio} €</td>
            </tr>
        </c:forEach>
    </table>
</body>
</html>

JSTL (JavaServer Pages Standard Tag Library)

JSTL es una biblioteca de etiquetas que facilita las tareas comunes en JSP:

Para usarla, primero debemos añadir la dependencia:

<dependency>
    <groupId>jakarta.servlet.jsp.jstl</groupId>
    <artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
    <version>2.0.0</version>
</dependency>
<dependency>
    <groupId>org.glassfish.web</groupId>
    <artifactId>jakarta.servlet.jsp.jstl</artifactId>
    <version>2.0.0</version>
</dependency>

Ejemplos de uso de JSTL:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>

<!-- Condicionales -->
<c:if test="${not empty param.nombre}">
    <p>Bienvenido, ${param.nombre}</p>
</c:if>

<c:choose>
    <c:when test="${producto.precio < 100}">
        <p>Precio bajo</p>
    </c:when>
    <c:when test="${producto.precio < 500}">
        <p>Precio medio</p>
    </c:when>
    <c:otherwise>
        <p>Precio alto</p>
    </c:otherwise>
</c:choose>

<!-- Bucles -->
<c:forEach var="i" begin="1" end="5">
    <p>Elemento ${i}</p>
</c:forEach>

<!-- Formato -->
<p>Precio: <fmt:formatNumber value="${producto.precio}" type="currency" currencySymbol="€" /></p>
<p>Fecha: <fmt:formatDate value="${ahora}" pattern="dd/MM/yyyy HH:mm" /></p>

Manejo de sesiones y cookies

Trabajando con sesiones

Las sesiones nos permiten mantener el estado entre peticiones:

// En un Servlet
HttpSession session = request.getSession();
session.setAttribute("usuario", "admin");
session.setAttribute("carrito", listaProductos);

// Tiempo de expiración (en segundos)
session.setMaxInactiveInterval(1800); // 30 minutos

// Eliminar la sesión
session.invalidate();
<!-- En un JSP -->
<p>Usuario: ${sessionScope.usuario}</p>
<p>Productos en carrito: ${sessionScope.carrito.size()}</p>

Manejo de cookies

Las cookies son pequeños datos almacenados en el navegador:

Aplicación de ejemplo: Gestión de tareas

Para consolidar lo aprendido, crearemos una pequeña aplicación de gestión de tareas con Servlets y JSP:

1. Modelo (JavaBean):

package es.miempresa.modelo;

import java.util.Date;

public class Tarea {
    private int id;
    private String titulo;
    private String descripcion;
    private boolean completada;
    private Date fechaCreacion;
    
    // Constructor, getters y setters
    public Tarea(int id, String titulo, String descripcion) {
        this.id = id;
        this.titulo = titulo;
        this.descripcion = descripcion;
        this.completada = false;
        this.fechaCreacion = new Date();
    }
    
    // Getters y setters (omitidos por brevedad)
}

2. Controlador (Servlet):

@WebServlet("/tareas/*")
public class TareasServlet extends HttpServlet {
    
    // Lista de tareas (en producción usaríamos una BD)
    private List<Tarea> tareas = new ArrayList<>();
    private int contadorId = 1;
    
    @Override
    public void init() {
        // Añadir algunas tareas de ejemplo
        tareas.add(new Tarea(contadorId++, "Aprender Servlets", "Estudiar la API de Servlets"));
        tareas.add(new Tarea(contadorId++, "Practicar JSP", "Crear páginas dinámicas con JSP"));
    }
    
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        String accion = request.getPathInfo();
        
        if (accion == null || accion.equals("/")) {
            // Mostrar lista de tareas
            request.setAttribute("tareas", tareas);
            RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/vistas/lista-tareas.jsp");
            dispatcher.forward(request, response);
            
        } else if (accion.equals("/nueva")) {
            // Mostrar formulario de nueva tarea
            RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/vistas/form-tarea.jsp");
            dispatcher.forward(request, response);
            
        } else if (accion.startsWith("/completar/")) {
            // Marcar tarea como completada
            int id = Integer.parseInt(accion.substring(11));
            for (Tarea tarea : tareas) {
                if (tarea.getId() == id) {
                    tarea.setCompletada(true);
                    break;
                }
            }
            response.sendRedirect(request.getContextPath() + "/tareas");
        }
    }
    
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        // Procesar formulario de nueva tarea
        String titulo = request.getParameter("titulo");
        String descripcion = request.getParameter("descripcion");
        
        if (titulo != null && !titulo.trim().isEmpty()) {
            tareas.add(new Tarea(contadorId++, titulo, descripcion));
        }
        
        response.sendRedirect(request.getContextPath() + "/tareas");
    }
}

3. Vistas (JSP):

lista-tareas.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<!DOCTYPE html>
<html>
<head>
    <title>Gestión de Tareas</title>
    <style>
        .tarea { margin-bottom: 15px; padding: 10px; border: 1px solid #ddd; }
        .completada { background-color: #e8f5e9; }
        .pendiente { background-color: #fffde7; }
    </style>
</head>
<body>
    <h1>Mis Tareas</h1>
    
    <a href="${pageContext.request.contextPath}/tareas/nueva">Nueva Tarea</a>
    
    <div>
        <c:choose>
            <c:when test="${empty tareas}">
                <p>No hay tareas disponibles.</p>
            </c:when>
            <c:otherwise>
                <c:forEach var="tarea" items="${tareas}">
                    <div class="tarea ${tarea.completada ? 'completada' : 'pendiente'}">
                        <h3>${tarea.titulo}</h3>
                        <p>${tarea.descripcion}</p>
                        <p>Creada: <fmt:formatDate value="${tarea.fechaCreacion}" pattern="dd/MM/yyyy HH:mm" /></p>
                        
                        <c:if test="${!tarea.completada}">
                            <a href="${pageContext.request.contextPath}/tareas/completar/${tarea.id}">Marcar como completada</a>
                        </c:if>
                    </div>
                </c:forEach>
            </c:otherwise>
        </c:choose>
    </div>
</body>
</html>

form-tarea.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
    <title>Nueva Tarea</title>
    <style>
        form { max-width: 500px; margin: 0 auto; }
        label, input, textarea { display: block; margin-bottom: 10px; width: 100%; }
        textarea { height: 100px; }
    </style>
</head>
<body>
    <h1>Nueva Tarea</h1>
    
    <form action="${pageContext.request.contextPath}/tareas" method="post">
        <div>
            <label for="titulo">Título:</label>
            <input type="text" id="titulo" name="titulo" required>
        </div>
        
        <div>
            <label for="descripcion">Descripción:</label>
            <textarea id="descripcion" name="descripcion"></textarea>
        </div>
        
        <div>
            <button type="submit">Guardar</button>
            <a href="${pageContext.request.contextPath}/tareas">Cancelar</a>
        </div>
    </form>
</body>
</html>

Resumen

En este artículo hemos explorado los fundamentos de los Servlets y JSP, dos tecnologías clave para el desarrollo web con Java. Hemos aprendido cómo los Servlets procesan peticiones HTTP y generan respuestas dinámicas, mientras que los JSP facilitan la creación de vistas mezclando HTML con código Java. También hemos visto cómo integrar ambas tecnologías siguiendo el patrón MVC, donde los Servlets actúan como controladores, los JavaBeans como modelos y los JSP como vistas.

Estos conocimientos constituyen la base del desarrollo web con Java y son fundamentales antes de adentrarse en frameworks más avanzados como Spring o Jakarta EE. En próximos artículos exploraremos cómo estos frameworks facilitan y mejoran el desarrollo de aplicaciones web, construyendo sobre los conceptos aprendidos aquí.