注意, 这里使用的是 Linux + Tomcat 7.x + JDK 7 + Nginx + Spring Session 的方式

安装Redis

Redis.io

配置Tomcat

这里为了简单,直接复制两份Tomcat,分别将目录命名为spring_session_master, spring_session_slave。 然后还要修改各个Tomcat下的conf/server.xml配置文件,修改下其中的相关端口。

我这里,将master的改为了9090端口,将slave的改为了9096端口.并且一起启动它.

配置Nginx

upstream springsession {
    server localhost:9090;
    server localhost:9096;
}
server {
	listen 80 default_server;
	server_name localhost;
	location / {
                proxy_pass http://springsession;
    }
}

然后重启下Nginx: sudo nginx -s reload

Spring Session 的使用

Spring中的spring.xml配置文件

<?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:task="http://www.springframework.org/schema/task" xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="
			http://www.springframework.org/schema/beans
			http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
			http://www.springframework.org/schema/context
			http://www.springframework.org/schema/context/spring-context-3.2.xsd
			http://www.springframework.org/schema/task
			http://www.springframework.org/schema/task/spring-task-3.2.xsd
			http://www.springframework.org/schema/aop
			http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
			http://www.springframework.org/schema/tx
			http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
			http://www.springframework.org/schema/mvc
			http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">

	<!-- scan package -->
	<context:component-scan base-package="test.yang.test" />
	<context:annotation-config />
	<mvc:annotation-driven />

	<bean id="propertyConfigurer"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<list>
				<value>classpath:core.properties</value>
			</list>
		</property>
		<property name="ignoreUnresolvablePlaceholders" value="true" />
	</bean>

	<import resource="session.xml" />
</beans>

Spring中的关于session的配置文件session.xml的内容

<?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:task="http://www.springframework.org/schema/task" xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="
			http://www.springframework.org/schema/beans
			http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
			http://www.springframework.org/schema/context
			http://www.springframework.org/schema/context/spring-context-3.2.xsd
			http://www.springframework.org/schema/task
			http://www.springframework.org/schema/task/spring-task-3.2.xsd
			http://www.springframework.org/schema/aop
			http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
			http://www.springframework.org/schema/tx
			http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">

	<context:annotation-config />
	<bean
		class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration" />


	<bean
		class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
		p:port="${redis.port}"
		p:hostName="${redis.host}"
		p:database="${redis.database}"/>

</beans>

SpringMVC中的spring-servlet.xml文件内容

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.2.xsd
        http://www.springframework.org/schema/mvc
		http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">

	<context:property-placeholder location="classpath:core.properties"
		ignore-unresolvable="true" order="2" />

	<mvc:resources location="/res/" mapping="/res/**" />
	<mvc:annotation-driven />
	<context:component-scan base-package="test.yang.test" />

	<bean id="velocityConfig"
		class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
		<property name="resourceLoaderPath" value="/WEB-INF/velocity" />
		<property name="velocityProperties">
			<props>
				<prop key="input.encoding">utf-8</prop>
				<prop key="output.encoding">utf-8</prop>
				<prop key="file.resource.loader.cache">false</prop>
			</props>
		</property>
	</bean>

	<bean id="viewResolver"
		class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
		<property name="cache" value="true" />
		<property name="prefix" value="" />
		<property name="suffix" value=".vm" />
		<property name="contentType" value="text/html; charset=utf-8" />
		<property name="exposeRequestAttributes" value="true" />
		<property name="exposeSessionAttributes" value="true" />
		<property name="exposeSpringMacroHelpers" value="true" />
	</bean>

</beans>

应用的 web.xml 文件内容

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	version="2.5">
	<display-name>uniweibov2</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>

	<session-config>
		<session-timeout>45</session-timeout>
	</session-config>

	<error-page>
		<error-code>404</error-code>
		<location>/error</location>
	</error-page>

	<error-page>
		<error-code>500</error-code>
		<location>/error</location>
	</error-page>

	<error-page>
		<error-code>504</error-code>
		<location>/error</location>
	</error-page>

	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
			/WEB-INF/conf/spring.xml
		</param-value>
	</context-param>


	<filter>
		<filter-name>CharacterEncodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>utf-8</param-value>
		</init-param>
		<init-param>
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>

	<filter-mapping>
		<filter-name>CharacterEncodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<filter>
		<filter-name>HiddenHttpMethodFilter</filter-name>
		<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
	</filter>

	<filter-mapping>
		<filter-name>HiddenHttpMethodFilter</filter-name>
		<servlet-name>mvc-dispatcher</servlet-name>
	</filter-mapping>

	<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>


	<servlet>
		<servlet-name>mvc-dispatcher</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/conf/spring-servlet.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>mvc-dispatcher</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
</web-app>

项目的pom.xml文件内容

    <properties>
		<spring.version>3.2.6.RELEASE</spring.version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>


    <dependencies>
		<dependency>
                <groupId>org.springframework.session</groupId>
                <artifactId>spring-session-data-redis</artifactId>
                <version>1.0.1.RELEASE</version>
        </dependency>

    	<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aop</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-beans</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aspects</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context-support</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.amqp</groupId>
			<artifactId>spring-amqp</artifactId>
			<version>1.1.4.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.amqp</groupId>
			<artifactId>spring-rabbit</artifactId>
			<version>1.1.4.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-redis</artifactId>
			<version>1.1.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-redis</artifactId>
			<version>1.1.0.RELEASE</version>
		</dependency>

		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
			<version>2.1.0</version>
		</dependency>

		<dependency>
			<groupId>cglib</groupId>
			<artifactId>cglib</artifactId>
			<version>2.2</version>
		</dependency>

		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>1.0.0</version>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-core</artifactId>
			<version>1.0.0</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.6.4</version>
		</dependency>

		<dependency>
			<groupId>org.codehaus.jackson</groupId>
			<artifactId>jackson-mapper-asl</artifactId>
			<version>1.9.8</version>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.0.1</version>
		</dependency>
		<dependency>
			<groupId>org.apache.velocity</groupId>
			<artifactId>velocity</artifactId>
			<version>1.7</version>这
		</dependency>
		<dependency>
			<groupId>org.apache.velocity</groupId>
			<artifactId>velocity-tools</artifactId>
			<version>2.0</version>
		</dependency>
	</dependencies>

core.properties文件内容

#redis server
redis.host = 10.0.0.40
redis.port = 6379
redis.database = 8

Controller示例代码Hello.java

package test.yang.test;

import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/hello")
public class Hello {
	@RequestMapping("/index")
	public void name(HttpSession session) {
		session.setAttribute("hello", "world");
		System.out.println(session.getId());
	}

	@RequestMapping("/get")
	public void get(HttpSession session) {
		String world = (String) session.getAttribute("hello");
		System.out.println(session.getId());
		System.out.println("结果为:" + world);
	}
}

Spring Session 原理

为什么按上面这样子直接配置就可以直接实现Session共享了呢?让我们一步一步来解释。

首先是session.xml文件:

<context:annotation-config />
	<bean
		class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration" />

这段代码,就实现了Spring Session中使用redis作为存储session的配置文件。进入源码,可以发现,它使用了RedisTemplate以及import org.springframework.session.web.http.SessionRepositoryFilter

SpringSession实现了HttpSession的接口来处理Session相关的业务操作,它提供了两种解决方案,一种是默认的使用Map来保存Session,还有一种,是为了在集群中解决Session共享的方案的使用Redis来处理Session数据。

它们分别是接口org.springframework.session.SessionRepository(即处理Session的接口)的两个实现: org.springframework.session.MapSessionRepositoryorg.springframework.session.RedisOperationsSessionRepository

然后,它们在SessionRepositoryFilter,被注入进来处理对应的Session实现的策略方式。注意,这个是Filter,为了在Spring中生效,所以在web.xml中添加了

	<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>

它是一个代理过滤器,它会在Spring Context里,查看实现了Filter的Bean,然后调用doFilter方法来进代理。因为SessionRepositoryFilter需要一些Spring Context的处理。所以才要在web.xml里配置这段过滤器的代理过滤器。 整个流程就是这样子了。再详细的, 可以根据此思想,深入每一个相关的类的源码。或者直接进行单步调试。

测试

全部启动完毕后, 每将访问http://localhost/hello/index,就可以看到tomcat的控制台里,打印sessionID的内容了,因为默认情况下是轮询的方式,所以master和slave会循环显示。即第一次请求在一个tomcat的控制台显示,下一次请求就会在另一个Tomcat里显示,如此循环。

然后可以关闭一个Tomcat(模拟进程死掉,或重新部署项目时,先让一个运行着,升级另一个Tomcat的应用,完成后再切换),可以看到sessionId没有变。当再次启动这个Tomcat时, 发现负载均衡又正常工作了,而且SessionID没有变。