一个简单的分布式session框架

时间:2023-03-09 02:53:13
一个简单的分布式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