En este tutorial veremos cómo utilizar Spring Session en nuestras aplicaciones web.
Índice de contenidos
- 1. Introducción
- 2. Motivación
- 3. Entorno
- 4. Creación del proyecto
- 5. Ejemplo práctico
- 6. Conclusiones
- 7. Referencias
1. Introducción
En este tutorial vamos a ver un nuevo módulo de Spring, que es Spring Session. Spring Session ofrece una API y las implementaciones de la gestión de la sesión de un usuario. También proporciona integración transparente con:
- HTTPSession: permite la sustitución de la HttpSession en un contenedor de aplicaciones de forma neutral. Las características adicionales incluyen :
- Sesiones en cluster: Permite el soporte de las sesiones en cluster sin estar atado a una solución específica contenedor de aplicaciones
- Múltiple Sesiones en un navegador: Permite tener varias sesiones en un mismo navegador
- RESTful APIs: Proporcionar identificadores de sesión en las cabeceras de las API REST
- WebSocket: proporciona la capacidad de mantener el HttpSession con vida al recibir mensajes WebSocket.
2. Motivación
Las cosas se vuelven más difíciles al escalar porque cada solicitud debe ser asociado con su correspondiente sesión que pueden residir en otro servidor.
Para superar esto, los provedores de servidores han implementado diversos tipos de replicación de sesión entre sus servidores. Alternativamente,
balanceadores de carga se pueden configurar para utilizar sesiones pegajosas. Ambos soluciones de trabajo, pero con la Spring Sesión ha creado otra opción.
Este tutorial va a mostrar cómo utilizar Spring Session, en concreto, la sesión en cluster.
3. Entorno
El tutorial está escrito usando el siguiente entorno:
- Hardware: MacBook Pro 15′ (2.5 GHz Intel Core i7, 16GB DDR3 SDRAM)
- Sistema Operativo: Mac OS X Yosemite 10.10.5
- NVIDIA GeForce GT 750M 2048 MB
- Eclipse Luna 4.4.2
- Java 1.8
- Docker 1.9.0
- Docker-compose 1.5.0
- Spring Session 1.0.2.RELEASE
- Spring Framework 4.1.6.RELEASE
- Tomcat 8.0.28
- Redis 3.0.5
- curl 7.43.0
4. Creación del proyecto
Vamos a crear una proyecto web muy simple que en sesión va a guardar un contador:
- Con una petición POST aumentamos el contador
- Con una petición GET recuperamos el contador
Lo primero es crear un proyecto web con maven y añadimos las dependencias de Spring y Spring Session, como puede verse a continuación.
pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>autentia</groupId> <artifactId>spring-session</artifactId> <version>1.0.0</version> <packaging>war</packaging> <properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring.version>4.1.6.RELEASE</spring.version> <spring.session.version>1.0.2.RELEASE</spring.session.version> <slf4j.version>1.7.12</slf4j.version> <log4j.version>2.2</log4j.version> <junit.version>4.12</junit.version> <hamcrest.version>1.3</hamcrest.version> <mockito.version>1.10.19</mockito.version> <redis.port>6379</redis.port> <redis.host>localhost</redis.host> </properties> <profiles> <profile> <id>desarrollo</id> <activation> <activeByDefault>true</activeByDefault> </activation> </profile> <profile> <id>docker</id> <properties> <redis.port>6379</redis.port> <redis.host>redis</redis.host> </properties> </profile> </profiles> <build> <finalName>springSession</finalName> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> </resources> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.3</version> <configuration> <webResources> <resource> <directory>src/main/webapp</directory> <includes> <include>WEB-INF/web.xml</include> </includes> </resource> </webResources> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-release-plugin</artifactId> <version>2.4</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.5.1</version> <configuration> <source>{java.version}</source> <target>${java.version}</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.2.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- ======================== Spring Session ============================ --> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> <version>${spring.session.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> <!-- ======================== Logging ============================ --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>${log4j.version}</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>${log4j.version}</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>${log4j.version}</version> </dependency> <!-- ====================== Tests ====================== --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-library</artifactId> <version>${hamcrest.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>${mockito.version}</version> <scope>test</scope> </dependency> </dependencies> </project> |
A continuación es crear nuestro servlet que implemente los métodos GET y POST, para recuperar y guardar nuestro contador en sesión.
ContadorServlet.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
package es.autentia.spring.session; import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.annotation.*; import javax.servlet.http.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @WebServlet(urlPatterns = { "/contadorServlet" }) public class ContadorServlet extends HttpServlet { private static final long serialVersionUID = -3450969163801147075L; protected static final String CONTADOR = "contador"; private static final Logger LOG = LoggerFactory.getLogger(ContadorServlet.class); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { final Integer contador = getContador(request); LOG.debug("Se envia como valor de contador {} en la session {} ", contador, request.getSession().getId()); try (ServletOutputStream out = response.getOutputStream()) { out.println(contador); } catch (IOException e) { LOG.error("Error read outputStram cause {}",e.getMessage()); } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { final int value = getContador(request).intValue() + 1; request.getSession().setAttribute(CONTADOR, value); LOG.debug("Se guarda como valor de contador {} en la session {} ", value, request.getSession().getId()); } private Integer getContador(HttpServletRequest request) { return Optional.ofNullable(request.getSession()) .map(session -> (Integer)session.getAttribute(CONTADOR)).orElse(0); } } |
Por último es la configuración de Spring y Spring session en nuestra aplicación web.
web.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <display-name>spring-session</display-name> <!-- Spring --> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath*:spring-configuracion/*.xml </param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <!-- Spring Session --> <filter> <filter-name>springSessionRepositoryFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSessionRepositoryFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app> |
Como vemos solo tenemos que añadir un filtro en nuestro web.xml, para terminar la configuración de Spring Session vamos activarlo por vía xml y además creamos la conexión de redis para que Spring Session guarde la sesión.
web.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd"> <context:property-placeholder location="classpath*:redis/redis.properties" ignore-unresolvable="true" order="1"/> <context:annotation-config /> <bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"> </bean> <bean class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:port="${spring.redis.port}" p:hostName="${spring.redis.host}" /> </beans> |
Como vemos tenemos un fichero propiedades donde tenemos los parámetros de configuración de conexión redis:
redis.properties
1 2 |
spring.redis.port={redis.port} spring.redis.host=${redis.host} |
Con esto ya tenemos creado nuesto proyecto web con Spring Session.
5. Ejemplo práctico
Para realizar el ejemplo práctico vamos a utilizar docker para desplegar nuestra aplicación en un contenedor y así poder escalar y comprobar que la Session es la misma teniendo varias instancias de nuestra aplicación web.
Para ello vamos a usar docker-compose para tener todas la configuración de nuestros contenedores en un único fichero.
docker-compose.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 |
bbdd: image: redis expose: - 6379 app: image: tomcat:8.0.28-jre8 links: - bbdd:redis ports: - 8080 volumes: - ./webapps:/usr/local/tomcat/webapps |
Como vemos tenemos definidos dos contenedores:
- Un contenedor llamado bbdd que contiene una instalación de redis con la versión 3.0.5 y expone el puerto 6379 en la red interna de docker.
- Un contenedor llamado app que contiene un tomcat con la version 8.0.28, expone el puerto 8080 en la red interna de docker, hacia fuera docker le asignara una aleatoria, un enlace con el contenedor bbdd y volumes para que tomcat lea nuestro war.
Todas las imágenes son oficiales proporciona docker hub.
Una vez creado nuestro war y nuestro docker-compose.yml procedemos a ejecutar nuestro docker-compose para instanaciar nuestro contendores con el siguiente comando
1 |
docker-compose up |
Como vemos sale por pantalla los logs de nuestros contenedores

Abrimos otra pestaña para obtener la Ip de la máquina donde se esta ejecutando docker y el puerto que tiene asignado tomcat
1 |
docker-machine ip dev |
Donde “dev” es el nombre de la máquina virtual donde se esta ejecutando docker y por último para saber el puerto ejecutamos el comando
1 |
docker ps |

Como podemos ver en este caso es el puerto 32770 con esta información podemos ya hacer la pruebas
Lo primero es ejecutar con el comando curl una petición GET a nuestro servlet para obtener nuestra sessionId
1 |
curl -i $(docker-machine ip dev):32770/springSession/contadorServlet |
Como vemos nuestro contador tiene una valor cero y nuestra sessionId es 6f20cb24-cb49-4647-84a1-b0da57ac86eb, a continuación vamos a aumentar nuestro contrado con el siguiente comando
1 |
curl -i -X POST -H "Cookie: SESSION=6f20cb24-cb49-4647-84a1-b0da57ac86eb" $(docker-machine ip dev):32770/springSession/contadorServlet |
Ahora volvemos a consultar el valor del contador con el siguiente comando
1 |
curl -i -H "Cookie: SESSION=6f20cb24-cb49-4647-84a1-b0da57ac86eb" $(docker-machine ip dev):32770/springSession/contadorServlet |
Por último vamos escalar la aplicacion app para tener dos instancia de nuestra aplicacion web, con el siguiente comando
1 |
docker-compose scale app=2 |
Ejecutamos el comando docker ps para verificar que tenemos dos contenedores de app

Para finalizar nuestra prueba vamos a realizar una peticón GET al segundo contenedor que tiene el puerto 32771
1 |
curl -i -H "Cookie: SESSION=6f20cb24-cb49-4647-84a1-b0da57ac86eb" $(docker-machine ip dev):32771/springSession/contadorServlet |
Si todo esta bien, al ejecutar el comando veremos que nuestro contador tiene valor 1.
Con esto termina nuestra prueba con Spring Session.
6. Conclusiones
Como hemos visto, se puede integrar de una manera fácil y rápida Spring Session en nuestra aplicaciones y poder escalar nuestra aplicaciones sin necesidad de instalar un sistema de replicación de sesion en nuestro sevidores.
La idea detrás de Spring sesión es bastante sencillo :
- Crear un nuevo filtro de servlet
- Añadir el filtro a la cadena de filtros de nuestro servlet
- Conecte el filtro a la conexión Redis
Espero que el tutorial os haya animado a usar (o al menos probar) esta nuevo proyecto de Spring.