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

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.