java-redis-session.md
思路
很多时候服务器需要用集群的方式进行部署,这种情况有有一个问题需要解决,那就是Session。根据Java EE规范,HttpSession默认是由各个厂商自己来实现的。例如Tomcat默认的HttpSession就是用用Map来实现的。这种默认的实现在集群环境中有很大的问题,无法实现Session共享。这种应用内Session有个很大的问题是Session太多时会占用太多的堆内存,合理的思路是应该找专业的第三方缓存来存放Session。
很多应用现在都在使用Redis或者Memcache来做缓存,我们也可以把Session放到这种专业缓存中。
如何放进去?这是个问题。
有一个类叫做HttpServletRequestWrapper
,一看这个名字就知道它使用的是装饰器模式。我们可以从这里入手来用第三方缓存来存储Session。我们通过拦截器来把包装原来的HttpServletRequest
和HttpServletResponse
实现。1
2
3
4
5
6
7
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
request = new HttpServletRedisRequest((HttpServletRequest) request);
response = new HttpServletRedisResponse((HttpServletResponse) response, (HttpServletRequest) request);
chain.doFilter(request, response);
}
我们可以在Filter中的
init
方法中初始化Redis连接池,在destroy
方法中销毁连接
在HttpServletRedisRequest
里面我们重写getSession方法,替换成自己的Session实现。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
46public class HttpServletRedisRequest extends HttpServletRequestWrapper {
private HttpServletRequest request;
private String sid;
/**
* Constructs a request object wrapping the given request.
*
* @param request
* @throws IllegalArgumentException if the request is null
*/
public HttpServletRedisRequest(HttpServletRequest request) {
super(request);
this.request = request;
this.sid = request.getHeader(TOKEN_HEADER_NAME);
}
public HttpSession getSession() {
return this.getSession(true);
}
public HttpSession getSession(boolean create) {
if (sid != null && sid.length() > 0) {
return sessionManage.get(sid, this.getServletContext());
} else if (create) {
HttpSession session = sessionManage.newSession(this.getServletContext());
this.sid = session.getId();
return session;
} else {
return null;
}
}
public String getRequestedSessionId() {
return this.sid;
}
public void setAttribute(String name, Object o) {
super.setAttribute(name, o);
}
}
在HttpServletRedisResponse
中,把SessionID放到响应头或者Cookie中响应给浏览器。1
2
3
4
5
6
7
8
9
10
11
12public class HttpServletRedisResponse extends HttpServletResponseWrapper {
private HttpServletRequest request;
private HttpServletResponse response;
public HttpServletRedisResponse(HttpServletResponse response, HttpServletRequest request) {
super(response);
this.request = request;
this.response = response;
response.setHeader(TOKEN_HEADER_NAME, request.getRequestedSessionId());
}
}
装饰器模式详解
这里的tomcat的HttpRequest实现是org.apache.catalina.connector.RequestFacade
,这个类实现了HttpServletRequest
接口。
在HttpServletRequestWrapper
类中维护了一个要包装的request对象,而HttpServletRequestWrapper
类实现了HttpServletRequest
接口。在HttpServletRequestWrapper
的实现中,默认都是调用包装对象的方法。我们创建的包装类继承了HttpServletRequestWrapper
,在这里我们重写了getSession
方法,这里当我们调用getSession
方法时会调用自己写的实现。当我们调用其它方法时,会调用父类HttpServletRequestWrapper
实现,而在父类中方法的实现却是调用的被包装类的实现。这样就实现了被包装对象的增强。
这里的核心是,这个包装类。它和被包装对象实现同一个接口,在他内部引用了一个被包装对象。默认首先是调用的被包装对象的实现,当子类重写包装类时,这时在调用就加入了自定义的实现。