zl程序教程

您现在的位置是:首页 >  后端

当前栏目

spring-session源码解读-5

Spring源码 解读 session
2023-09-14 09:04:38 时间
Session在浏览器通常是通过cookie保存的,cookie里保存了jessionid,代表用户的session id。一个访问路径只有一个session cookie(事实上在客户端就只有一个cookie,jsessionid是作为cookie值的一部分,这里把cookie抽象成类似服务器端的实现),也就是一个访问路径在一个浏览器上只有一个se

Session在浏览器通常是通过cookie保存的,cookie里保存了jessionid,代表用户的session id。一个访问路径只有一个session cookie(事实上在客户端就只有一个cookie,jsessionid是作为cookie值的一部分,这里把cookie抽象成类似服务器端的实现),也就是一个访问路径在一个浏览器上只有一个session,这是绝大多数容器对session的实现。而spring却可以支持单浏览器多用户session。下面就看看spring是怎样去支持多用户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值结构。


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;

 }

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 Session 的原理 今天在写一个对外接口, 这个接口大致原理是在过滤器中通过 token 获取用户信息然后创建 session, 后续的流程就是 `Controller - Service - Dao` 了. 这次开发没有像之前那样愣头愣脑的, 我想了一下, 对方调用的时候是没有 session id 的, 也就是每次认证之后都会创建一个 session. 那这就可能存在一个大问题了, 假设调用次数非常多的话, 会创建茫茫多的 session, 可能会击垮系统.
19、springcloud分布式Session之Spring Session HttpSession是通过Servlet容器创建和管理的,像Tomcat/Jetty都是保存在内存中的。但是把应用搭建成分布式的集群,然后利用F5、LVS或Nginx做负载均衡,那么来自同一用户的Http请求将有可能被分发到多个不同的服务器中。
阿里特邀专家徐雷Java Spring Boot开发实战系列课程(第18讲):制作Java Docker镜像与推送到DockerHub和阿里云Docker仓库 立即下载