spring-session源码解读-5
2015-12-15 19:58
1201 查看
session通用策略
Session在浏览器通常是通过cookie保存的,cookie里保存了jessionid,代表用户的session id。一个访问路径只有一个session cookie(事实上在客户端就只有一个cookie,jsessionid是作为cookie值的一部分,这里把cookie抽象成类似服务器端的实现),也就是一个访问路径在一个浏览器上只有一个session,这是绝大多数容器对session的实现。而spring却可以支持单浏览器多用户session。下面就看看spring是怎样去支持多用户session的。对多用户session的支持
spring session通过增加session alias概念来实现多用户session,每一个用户都映射成一个session alias。当有多个session时,spring会生成“alias1 sessionid1 alias2 sessid2…….”这样的cookie值结构。spring session提交时如果有新session生成,会触发onNewSession动作生成新的session cookie
public void onNewSession(Session session, HttpServletRequest request, HttpServletResponse response) { Set<String> sessionIdsWritten = getSessionIdsWritten(request); if(sessionIdsWritten.contains(session.getId())) { return; } sessionIdsWritten.add(session.getId()); Map<String,String> sessionIds = getSessionIds(request); String sessionAlias = getCurrentSessionAlias(request); sessionIds.put(sessionAlias, session.getId()); Cookie sessionCookie = createSessionCookie(request, sessionIds); response.addCookie(sessionCookie); }
a) 确保已经存在cookie里的session不会再被处理。
b) 生成一个包含所有alias的session id的map,并通过这个map构造新的session cookie值。
createSessionCookie会根据一个alias-sessionid的map去构造session cookie。
private Cookie createSessionCookie(HttpServletRequest request, Map<String, String> sessionIds) { //cookieName是"SESSION",spring的session cookie都是 //以"SESSION"命名的 Cookie sessionCookie = new Cookie(cookieName,""); //省略部分非关键逻辑 if(sessionIds.isEmpty()) { sessionCookie.setMaxAge(0); return sessionCookie; } if(sessionIds.size() == 1) { String cookieValue = sessionIds.values().iterator().next(); sessionCookie.setValue(cookieValue); return sessionCookie; } StringBuffer buffer = new StringBuffer(); for(Map.Entry<String,String> entry : sessionIds.entrySet()) { String alias = entry.getKey(); String id = entry.getValue(); buffer.append(alias); buffer.append(" "); buffer.append(id); buffer.append(" "); } buffer.deleteCharAt(buffer.length()-1); sessionCookie.setValue(buffer.toString()); return sessionCookie; }
a) 当session被invalidate,可能会存在seesionids为空的情况,这种情况下将session cookie的最大失效时间设成立即。
b) 如果只有一个session id,则和普通session cookie一样处理,cookie值就是session id。
c) 如果存在多个session id,则生成前文提到的session cookie值结构。
session cookie的获取
getSessionIds方法会取出request里的session cookie值,并且对每种可能的值结构进行相应的格式化生成一个key-value的map。public Map<String,String> getSessionIds(HttpServletRequest request) { Cookie session = getCookie(request, cookieName); String sessionCookieValue = session == null ? "" : session.getValue(); Map<String,String> result = new LinkedHashMap<String,String>(); StringTokenizer tokens = new StringTokenizer(sessionCookieValue, " "); //单用户cookie的情况 if(tokens.countTokens() == 1) { result.put(DEFAULT_ALIAS, tokens.nextToken()); return result; } while(tokens.hasMoreTokens()) { String alias = tokens.nextToken(); if(!tokens.hasMoreTokens()) { break; } String id = tokens.nextToken(); result.put(alias, id); } return result; }
对单用户session cookie的处理,只取出值,默认为是默认别名(默认为0)用户的session。
对多用户,则依据值结构的格式生成alias-sessionid的map。
以上两种格式化都是对创建session的逆操作。
getCurrentSessionAlias用来获取当前操作用户。可以通过在request里附加alias信息,从而让spring可以判断是哪个用户在操作。别名是通过”alias name=alias”这样的格式传入的,alias name默认是_s,可以通过setSessionAliasParamName(String)方法修改。我们可以在url上或者表单里添加”_s=your user alias”这样的形式来指明操作用户的别名。如果不指明用户别名,则会认为是默认用户,可以通过setSessionAliasParamName(null)取消别名功能。
public String getCurrentSessionAlias(HttpServletRequest request) { if(sessionParam == null) { return DEFAULT_ALIAS; } String u = request.getParameter(sessionParam); if(u == null) { return DEFAULT_ALIAS; } if(!ALIAS_PATTERN.matcher(u).matches()) { return DEFAULT_ALIAS; } return u; }
触发session提交
spring会通过两个方面确保session提交:a) response提交,主要包括response的sendRedirect和sendError以及其关联的字节字符流的flush和close方法。
abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper { public OnCommittedResponseWrapper(HttpServletResponse response) { super(response); } /** * Implement the logic for handling the {@link javax.servlet.http.HttpServletResponse} being committed */ protected abstract void onResponseCommitted(); @Override public final void sendError(int sc) throws IOException { doOnResponseCommitted(); super.sendError(sc); } //sendRedirect处理类似sendError @Override public ServletOutputStream getOutputStream() throws IOException { return new SaveContextServletOutputStream(super.getOutputStream()); } @Override public PrintWriter getWriter() throws IOException { return new SaveContextPrintWriter(super.getWriter()); } private void doOnResponseCommitted() { if(!disableOnCommitted) { onResponseCommitted(); disableOnResponseCommitted(); } else if(logger.isDebugEnabled()){ logger.debug("Skip invoking on"); } } private class SaveContextPrintWriter extends PrintWriter { private final PrintWriter delegate; public SaveContextPrintWriter(PrintWriter delegate) { super(delegate); this.delegate = delegate; } public void flush() { doOnResponseCommitted(); delegate.flush(); } //close方法与flush方法类似 } //SaveContextServletOutputStream处理同字符流 }
onResponseCommitted的实现由子类SessionRepositoryResponseWrapper提供
private final class SessionRepositoryResponseWrapper extends OnCommittedResponseWrapper { private final SessionRepositoryRequestWrapper request; /** * @param response the response to be wrapped */ public SessionRepositoryResponseWrapper(SessionRepositoryRequestWrapper request, HttpServletResponse response) { super(response); if(request == null) { throw new IllegalArgumentException("request cannot be null"); } this.request = request; } @Override protected void onResponseCommitted() { request.commitSession(); } }
response提交后触发了session提交。
b) SessionRespositoryFilter
仅仅通过response提交时触发session提交并不能完全保证session的提交,有些情况下不会触发response提交,比如对相应资源的访问没有servlet处理,这种情况就需要通过全局filter做保证。
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { //省略 //filterChain会在所有filter都执行完毕后调用对应的servlet filterChain.doFilter(strategyRequest, strategyResponse); } finally { //所有的处理都完成后提交session wrappedRequest.commitSession() }
相关文章推荐
- Spring AOP实现方式
- JAVA设计模式之抽象工厂模式
- java设计模式之建造者模式
- Java中堆内存和栈内存详解
- JAVA设计模式之工厂模式(简单工厂模式+工厂方法模式)
- JAVA设计模式之原型模式
- JAVA设计模式之代理模式
- Eclipse无法启动报An internal error occurred during: "reload maven project". java.lang.NullPointerException
- Java内存管理:深入Java内存区域(深入理解Java虚拟机的第2章内容,加上个人浅显理解)
- JAVA设计模式之享元模式
- JAVA设计模式之门面模式(外观模式)
- 使用java开发文本全局搜索器
- JAVA设计模式初探之装饰者模式
- JAVA设计模式初探之组合模式
- JAVA设计模式初探之适配器模式
- java 垃圾回收机制
- Java中LOCK和synchronized区别
- JAVA设计模式初探之桥接模式
- java集合——树集(TreeSet)+对象的比较
- java进阶(一)--控制反转IoC