java-redis-session.md

思路

很多时候服务器需要用集群的方式进行部署,这种情况有有一个问题需要解决,那就是Session。根据Java EE规范,HttpSession默认是由各个厂商自己来实现的。例如Tomcat默认的HttpSession就是用用Map来实现的。这种默认的实现在集群环境中有很大的问题,无法实现Session共享。这种应用内Session有个很大的问题是Session太多时会占用太多的堆内存,合理的思路是应该找专业的第三方缓存来存放Session。
很多应用现在都在使用Redis或者Memcache来做缓存,我们也可以把Session放到这种专业缓存中。

如何放进去?这是个问题。

有一个类叫做HttpServletRequestWrapper,一看这个名字就知道它使用的是装饰器模式。我们可以从这里入手来用第三方缓存来存储Session。我们通过拦截器来把包装原来的HttpServletRequestHttpServletResponse实现。

1
2
3
4
5
6
7
@Override
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
46
public 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);
}


@Override
public HttpSession getSession() {
return this.getSession(true);
}

@Override
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;
}
}

@Override
public String getRequestedSessionId() {
return this.sid;
}

@Override
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
12
public 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实现,而在父类中方法的实现却是调用的被包装类的实现。这样就实现了被包装对象的增强。

这里的核心是,这个包装类。它和被包装对象实现同一个接口,在他内部引用了一个被包装对象。默认首先是调用的被包装对象的实现,当子类重写包装类时,这时在调用就加入了自定义的实现。

本站采用「署名 4.0 国际」进行许可。