SessionListener: Gestión de eventos de sesión en aplicaciones web

En este tutorial vamos a ver un sencillo ejemplo que nos enseñará unas técnicas muy interesantes para poder saber cuando se ha creado/destruido una sesión o cuando se ha añadido/eliminado un objeto de ella.
  • 1. Introducción
  • 2. HttpSessionListener y HttpSessionBindingListener (javax.servlet.http)
  • 3. Demostración
  • 4. Conclusión

1. Introducción

Existen determinadas situaciones en proyectos Web en las que necesitamos saber cuando se ha creado/destruido una sesión o cuando se ha añadido/eliminado un objeto de ella. Con este tutorial pretendemos que os familiaricéis con este tipo de operaciones. Para ello vamos a implementar un pequeño ejemplo donde utilizaremos estas técnicas. Nuestra mini-aplicación se encargará de llevar un contador del número de usuarios que actualmente se han conectado a nuestra aplicación y mostrar cuantos de ellos se han logado.

2. HttpSessionListener y HttpSessionBindingListener (javax.servlet.http)

HttpSessionListener y HttpSessionBindingListener son dos Interfaces del paquete javax.servlet.http. Las clases que las implementen tendrán un comportamiento especial en nuestro contenedor. HttpSessionListener contiene dos métodos: sessionCreated y sessionDestroyed utilizados por el contenedor de aplicaciones para notificarnos cuando una sesión de un usuario ha sido creada o destruida. Aquellas clases que implementen este interfaz deberán ser registradas como listener en nuestro fichero web.xml (en nuestro ejemplo la implementa la clase es.adictos.websession.WebSessionListener). Por otro lado, HttpSessionBindingListener, contiene también dos métodosvalueBound y valueUnBound invocados por el contenedor para indicar a la clase que lo implemente que dicho objeto ha sido insertado o eliminado de la sesión (en nuestro ejemplo la implementa la clase es.adictos.websession.bean.User).

Además de estos interfaces, el paquete contiene otros dos HttpSessionActivationListener yHttpSessionAttributeListener. El primero se utiliza para tener un control cuando utilizamos sesiones distribuidas. Sus métodos serán llamados antes de serializarse (sessionWillPassivate) para ser enviados a otra Maquina Virtual, y cuando son deserializados (sessionDidActivate) para que la sesión sea utilizada en otra Maquina Virtual. La clase que implemente el segundo deberá ser añadida al fichero web.xml como HttpSessionListener. HttpSessionAttributeListenercontiene tres métodos llamados cuando un objeto es añadido a la sesión (attributeAdded), eliminado (attributeRemoved) o remplazado (attributeReplaced). No lo hemos comentado pero la gran mayoría de los interfaces comentados están disponibles a partir de la especificación Servlet 2.3.

3. Demostración

Los contadores que mantienen el número de usuarios conectados y logados deben estar almacenados en el Contexto del contenedor y cualquiera que intente modificarlos u obtener su valor deberá utilizar una región crítica. Esto es necesario porque pueden producirse situaciones donde varios hilos en paralelo puedan estar operando con ellos lo que puede provocar inconsistencias.

Como hemos comentado antes se ha creado la clase WebSessionListener encargada de mantener el contador del número de usuarios que se encuentran conectados. El método sessionCreated aumentará el contador «usuariosConectados», mientras sessionDestroyed lo diminuirá.

package es.adictos.websession;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
public class WebSessionListener implements HttpSessionListener {

public void sessionCreated(HttpSessionEvent arg0) {

System.out.println(«Session creada»);
ServletContext contexto = arg0.getSession().getServletContext();
synchronized (contexto) {

Integer usuarioConectados = (Integer) contexto.getAttribute(«usuariosConectados»);
if (usuarioConectados == null) {
usuarioConectados = new Integer(0);
}
usuarioConectados+=1;
contexto.setAttribute(«usuariosConectados», usuarioConectados);

}

}
public void sessionDestroyed(HttpSessionEvent arg0) {

System.out.println(«Session destruida»);
ServletContext contexto = arg0.getSession().getServletContext();
synchronized (contexto) {

Integer usuarioConectados = (Integer) contexto.getAttribute(«usuariosConectados»);
if (usuarioConectados == null) {
usuarioConectados = new Integer(0);
}
usuarioConectados-=1;
contexto.setAttribute(«usuariosConectados», usuarioConectados);

}

}

}

Por otro lado, tenemos la clase User, un Bean que almacena los datos del usuario y que también se encarga de incrementar (valueBound) o decrementar (valueUnBound) el contador de usuarios logados en la aplicación.

package es.adictos.websession.bean;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
public class User implements HttpSessionBindingListener {

public void valueBound(HttpSessionBindingEvent arg0) {

System.out.println(«User añadido a la session»);
ServletContext contexto = arg0.getSession().getServletContext();
synchronized (contexto) {

Integer usuarioLogados = (Integer) contexto.getAttribute(«usuariosLogados»);
if ( usuarioLogados == null) {
usuarioLogados = new Integer(0);
}
usuarioLogados+=1;
contexto.setAttribute(«usuariosLogados», usuarioLogados);

}

}
public void valueUnbound(HttpSessionBindingEvent arg0) {

System.out.println(«User eliminado de session»);
ServletContext contexto = arg0.getSession().getServletContext();
synchronized (contexto) {

Integer usuarioLogados = (Integer) contexto.getAttribute(«usuariosLogados»);
if ( usuarioLogados == null) {
usuarioLogados = new Integer(0);
}
usuarioLogados-=1;
contexto.setAttribute(«usuariosLogados», usuarioLogados);

}

}

}

Unido a estas dos clases se han desarrollado tres servlet: Login que almacena el usuario en la sesión, Logout que elimina de la sesión al usuario y Home que muestra el acceso al portal. Todos ellos serán redirigidos a una jsp denominada «estado_session.jsp» que muestra el estado del número de usuarios conectados/logados y un botón con la acción Login o Logout dependiendo de si el usuario se encuentra en sesión o no. A continuación se muestra la implementación de cada componente:

Home

package es.adictos.websession.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Home extends HttpServlet{

@Override
protected void doGet(HttpServletRequest arg0, HttpServletResponse arg1) throws ServletException, IOException {
doPost(arg0, arg1);
}
@Override
protected void doPost(HttpServletRequest arg0, HttpServletResponse arg1) throws ServletException, IOException {
getServletContext().getRequestDispatcher(«/estado_session.jsp»).forward(arg0, arg1);
}

}

Login

package es.adictos.websession.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import es.adictos.websession.bean.User;
public class Login extends HttpServlet {

@Override
protected void doGet(HttpServletRequest arg0, HttpServletResponse arg1) throws ServletException, IOException {
doPost(arg0, arg1);
}
@Override
protected void doPost(HttpServletRequest arg0, HttpServletResponse arg1) throws ServletException, IOException {
arg0.getSession().setAttribute(«usuario», new User());
getServletContext().getRequestDispatcher(«/estado_session.jsp»).forward(arg0, arg1);
}

}

Logout

package es.adictos.websession.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Logout extends HttpServlet {

@Override
protected void doGet(HttpServletRequest arg0, HttpServletResponse arg1) throws ServletException, IOException {
doPost(arg0, arg1);
}
@Override
protected void doPost(HttpServletRequest arg0, HttpServletResponse arg1) throws ServletException, IOException {
arg0.getSession().removeAttribute(«usuario»);
getServletContext().getRequestDispatcher(«/estado_session.jsp»).forward(arg0, arg1);
}

}

estado_session.jsp

<% ServletContext contexto = getServletContext();
Integer usuarioConectados= null;
Integer usuarioLogados = null;
synchronized (contexto) {
usuarioConectados= (Integer) contexto.getAttribute(«usuariosConectados»);
usuarioLogados = (Integer) contexto.getAttribute(«usuariosLogados»);
}
%>
<html>
<head><title>Adictos al trabajo: Estado Session</title></head>
<body>
<div>
<ul>
<li>
Usuario Logados: <%=(usuarioLogados==null) ? «0» : usuarioLogados.toString() %>
</li>
<li>
Usuario Conectados: <%=(usuarioConectados==null) ? «0» : usuarioConectados.toString() %>
</li>
</ul>
</div>
<%
if (session.getAttribute(«usuario») == null) {%>
<form action=»/WebSession/Login» method=»post»>
<input type=»submit» value=»Login»>
</form>
<% } else {%>
<form action=»/WebSession/Logout» method=»post»>
<input type=»submit» value=»Logout»>
</form>
<%}%>
</body>
</html>

Ahora mostramos el fichero web.xml, en el que se ha reducido al máximo el timeout de la sesión para mostrar la situación en la que el contenedor elimina la sesión por timeout.

<?xml version=»1.0″ encoding=»UTF-8″?>
<web-app id=»WebApp_ID» version=»2.4″ xmlns=»http://java.sun.com/xml/ns/j2ee» xmlns:xsi=»http://www.w3.org/2001/XMLSchema-instance» xsi:schemaLocation=»http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd»>

<display-name>
WebSession</display-name>
<welcome-file-list>

<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>

</welcome-file-list>
<listener>

<listener-class>es.adictos.websession.WebSessionListener</listener- class>

</listener>
<servlet>

<servlet-name>Login</servlet-name>
<servlet-class>es.adictos.websession.servlet.Login</servlet-class>< br>

</servlet>
<servlet>

<servlet-name>Logout</servlet-name>
<servlet-class>es.adictos.websession.servlet.Logout</servlet-class>< br>

</servlet>
<servlet>

<description>
</description>
<display-name>
Home</display-name>
<servlet-name>Home</servlet-name>
<servlet-class>
es.adictos.websession.servlet.Home</servlet-class>

</servlet>
<servlet-mapping>

<servlet-name>Login</servlet-name>
<url-pattern>/Login</url-pattern>

</servlet-mapping>

<servlet-mapping>
<servlet-name>Logout</servlet-name>
<url-pattern>/Logout</url-pattern>

</servlet-mapping>

<servlet-mapping>

<servlet-name>Home</servlet-name>
<url-pattern>/Home</url-pattern>

</servlet-mapping>

<session-config>
<session-timeout>1</session-timeout>
</session-config>

</web-app>

Y por último mostramos un ejemplo de ejecución.

[STDOUT] Session creada

[STDOUT] Session creada

[STDOUT] User añadido a la session

[STDOUT] Session destruida – Timeout
[STDOUT] Session destruida – Timeout
[STDOUT] User eliminado de session
[STDOUT] Session creada

3 Conclusiones

Como habéis podido comprobar la captura de eventos relacionados con la sesión es muy sencilla. Lo único que debemos implementar son los interfaces de que disponemos en el paquete javax.servlet.http.

Comentarios

4 respuestas

  1. Esta bueno esto, pero tengo un problema, tebgo una clase que implementa la Interfaz HttpSessionListener, en el metodo sessionDestroyed, tengo mi implementacion de lo que quiero hacer cuando se cierra la sesion, cuando ejecuto el metrodo sesion.invalidate(), se ejecuta mi implementacion de sessionDestroyed, y no hay problema. Pero cuando la sesion se pone inactiva por el tiempo cuando se va a ejecutar el metodo sessionDestroyed ya la sesion esta destruida. Por lo que me da error.

  2. Gracias por tu blog. Facilitas datos que me ayudará. Continúa así. Es un proyecto valioso que será de utilidad para muchas personas. Gracias.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

He leído y acepto la política de privacidad

Información básica acerca de la protección de datos

  • Responsable: IZERTIS S.A.
  • Finalidad: Envío información de carácter administrativa, técnica, organizativa y/o comercial sobre los productos y servicios sobre los que se nos consulta.
  • Legitimación: Consentimiento del interesado
  • Destinatarios: Otras empresas del Grupo IZERTIS. Encargados del tratamiento.
  • Derechos: Acceso, rectificación, supresión, cancelación, limitación y portabilidad de los datos.
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad

Consultor tecnológico de desarrollo de proyectos informáticos. Co-autor del libro "Actualización y mantenimiento del PC (Edición de 2010) publicado por Anaya Multimedia. Ingeniero Técnico en Informática de Sistemas e Ingeniero en Informática (premio al mejor expediente de su promoción). Puedes encontrarme en Autentia: Ofrecemos servicios de soporte a desarrollo, factoría y formación. Somos expertos en Java/Java EE

¿Quieres publicar en Adictos al trabajo?

Te puede interesar

22/06/2026

Juan Antonio Jiménez Torres

Vamos a instrumentalizar un SDD para un microservicio bancario. En este artículo sobre SDD - Spec Driven Development, instrumentalizado sobre OpenSpec vamos a construir una PoC de algo más serio que una tabla periódica. Un microservicio bancario muy simplificado que gestione cuentas y permita hacer transferencias

16/06/2026

Juan Antonio Jiménez Torres

Este es el segundo artículo sobre SDD, Spec Driven Development, instrumentalizado sobre OpenSpec. Pero esta vez nos acercaremos usando softwere gratuito. Por un lado OpenCode en lugar de ClaudeCode y usando el modelo gratuito Gemma4 de Google en lugar de otros de pago.

08/06/2026

Juan Antonio Jiménez Torres

Aprenderemos sobre SDD – Spec Driven Development instrumentalizado sobre OpenSpec. Haremos un primer acercamiento usando ClaudeCode con Sonnet, para luego, en un artículo posterior pasarnos a la versión gratuita de OpenCode con Gemma4