一个简单的分布式session框架
该代码只是用来学习原理的,有很多不完善之处。
代码: git@github.com:sicw/EasySpringSession.git
一. 整体设置
1. 实现Filter,封装新的request请求
2. 在newRequest中重写getSession
3. 在getSession中,从redis获取session,或存储session到redis
二. 过滤器
1. 封装request
2. 执行完过滤器链之后要设置sessionId到cookie
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if(!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)){ throw new RuntimeException("just supports HTTP requests"); } //封装新的请求 SessionRepositoryRequestWrapper newRequest = new SessionRepositoryRequestWrapper((HttpServletRequest) request, (HttpServletResponse) response); try { chain.doFilter(newRequest, response); }finally { //持久化session到redis //设置sessionId到cookie newRequest.commitSession(); } }
三. 封装Request
封装Request的主要目的是重写他的getSession操作。
public class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper { private HttpServletRequest httpServletRequest; private HttpServletResponse httpServletResponse; private HttpSession currentSession; public SessionRepositoryRequestWrapper(HttpServletRequest request,HttpServletResponse response) { super(request); httpServletRequest = request; httpServletResponse = response; } /** * 在servlet中调用getSession会触发该方法 * @return session */ @Override public HttpSession getSession() { HttpSession session = getRequestedSession(); //没有sessionId或sessionId已过期 if(session == null){ session = sessionRepository.createSession(); } currentSession = session; return session; } /** * 持久化session * 设置sessionId到Cookie */ public void commitSession(){ HttpSession session = currentSession; sessionRepository.save(session); String sessionId = session.getId(); httpSessionIdResolver.setSessionId(httpServletRequest, httpServletResponse, sessionId); } private HttpSession getRequestedSession() { //获取sessionId List<String> sessionIds = httpSessionIdResolver.resolveSessionIds(httpServletRequest); //获取session for (String sessionId : sessionIds) { HttpSession session = sessionRepository.findById(sessionId); if(session != null){ return session; } } return null; } }
四. 持久化session
这里持久化session使用的是spring-data-redis,把session通过HMSEt存储到redis中
@Override public RedisSession createSession() { return new RedisSession(); }
@Override public void save(HttpSession session) { ((RedisSession)session).saveDelta(); } @Override public RedisSession findById(String id) { return getSession(id, false); } @Override public void deleteById(String id) { redisTemplate.delete(id); }
五. 测试
1. 创建webapp
2. 实现SetServlet和GetServlet
@WebServlet("/get") public class GetServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String key = req.getParameter("name"); HttpSession session = req.getSession(); String value = (String) session.getAttribute(key); System.out.println("get ["+key+","+value+"]"); } }
@WebServlet("/set") public class SetServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String key = req.getParameter("name"); String value = req.getParameter("value"); HttpSession session = req.getSession(); session.setAttribute(key,value); System.out.println("set ["+key+","+value+"]"); } }
配置web.xml
因为我们的Filter需要设置属性,如果直接在这里配置Filter那么不能设置属性了。所以使用委派类DelegatingFilterProxy
他会根据Filter-Name去spring查找相应的bean,我们把Filter配置成bean就可以设置他的属性了。
<web-app> <display-name>Archetype Created Web Application</display-name> <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> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
applicationContext.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 加载配置文件 --> <context:property-placeholder location="classpath:redis.properties" /> <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxIdle" value="${redis.maxIdle}" /> <property name="maxTotal" value="${redis.maxTotal}" /> <property name="maxWaitMillis" value="${redis.maxWaitMillis}" /> <property name="blockWhenExhausted" value="${redis.blockWhenExhausted}" /> <property name="testOnBorrow" value="${redis.testOnBorrow}" /> </bean> <bean id="redisClusterConfiguration" class="org.springframework.data.redis.connection.RedisClusterConfiguration"> <property name="clusterNodes"> <set> <bean id="redisNode1" class="org.springframework.data.redis.connection.RedisNode"> <constructor-arg name="host" value="${redis.host1}"/> <constructor-arg name="port" value="${redis.port1}"/> </bean> <bean id="redisNode2" class="org.springframework.data.redis.connection.RedisNode"> <constructor-arg name="host" value="${redis.host2}"/> <constructor-arg name="port" value="${redis.port2}"/> </bean> <bean id="redisNode3" class="org.springframework.data.redis.connection.RedisNode"> <constructor-arg name="host" value="${redis.host3}"/> <constructor-arg name="port" value="${redis.port3}"/> </bean> <bean id="redisNode4" class="org.springframework.data.redis.connection.RedisNode"> <constructor-arg name="host" value="${redis.host4}"/> <constructor-arg name="port" value="${redis.port4}"/> </bean> <bean id="redisNode5" class="org.springframework.data.redis.connection.RedisNode"> <constructor-arg name="host" value="${redis.host5}"/> <constructor-arg name="port" value="${redis.port5}"/> </bean> <bean id="redisNode6" class="org.springframework.data.redis.connection.RedisNode"> <constructor-arg name="host" value="${redis.host6}"/> <constructor-arg name="port" value="${redis.port6}"/> </bean> </set> </property> </bean> <!-- Spring-redis连接池管理工厂 --> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <constructor-arg name="clusterConfig" ref="redisClusterConfiguration"/> <property name="timeout" value="${redis.timeout}" /> <property name="poolConfig" ref="poolConfig" /> <property name="usePool" value="true"/> </bean> <!-- redis template definition --> <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> <property name="connectionFactory" ref="jedisConnectionFactory" /> <property name="keySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" /> </property> <property name="valueSerializer"> <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" /> </property> <property name="hashKeySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" /> </property> <property name="hashValueSerializer"> <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" /> </property> </bean> <bean id="redisSessionRepository" class="com.channelsoft.ccod.session.RedisSessionRepository"> <constructor-arg name="redisTemplate" ref="redisTemplate"/> </bean>
<!-- 在这里配置了Filter,并且设置属性 --> <bean id="springSessionRepositoryFilter" class="com.channelsoft.ccod.filter.SessionRepositoryFilter"> <property name="sessionRepository" ref="redisSessionRepository"/> </bean> </beans>
redis.properties配置
#Common Config redis.password="" redis.maxIdle=400 redis.maxTotal=6000 redis.maxWaitMillis=1000 redis.blockWhenExhausted=true redis.testOnBorrow=true redis.timeout=100000 defaultCacheExpireTime=60 #Cluster Config redis.host1=10.130.29.83 redis.port1=6379 redis.host2=10.130.29.83 redis.port2=6380 redis.host3=10.130.29.83 redis.port3=6381 redis.host4=10.130.29.83 redis.port4=6382 redis.host5=10.130.29.83 redis.port5=6383 redis.host6=10.130.29.83 redis.port6=6384
最后配置容器端口,比如Tomcat创建两个配置端口分别为8080,8081,启动容器。
通过url访问:
http://localhost:8080/set?name=k1&value=v1
http://localhost:8081/get?name=k1