之前给大家介绍了springsession是做什么的的相关内容,那么下面要接着给大家讲到的就是spring session的实现原理。
首先的话我们先通过一张图片来看一下spring session的框架分析。
接下来再来谈一下spring session实现原理的内容。
spring session重写servlet request以及redis实现存储的相关问题。
了解一下spring session无缝替换应用服务器的request的大致原理:
自定义Filter,实现doFilter方法;继承HttpServletRequestWrapper 、HttpServletResponseWrapper类,重写getSession等相关方法;在第一步的doFilter当中new 第二步自定义的request和response的类,并且将他们别传递到过滤器链;将这个filter配置到过滤器链的第一个位置上;
/** 这个类是spring-session的1.30源码,也是实现上面第一到第三步的关键类 **/ public class SessionRepositoryFilter < S extends ExpiringSession > extends OncePerRequestFilter { /** session存储容器接口,redis、mongoDB、genfire等数据库都是实现该接口 **/ private final SessionRepository < S > sessionRepository; private ServletContext servletContext; /** sessionID的传递方式接口。目前spring-session自带两个实现类 1.cookie方式 :CookieHttpSessionStrategy 2.http header 方式:HeaderHttpSessionStrategy 当然,我们也可以自定义其他方式。 **/ private MultiHttpSessionStrategy httpSessionStrategy = new CookieHttpSessionStrategy(); public SessionRepositoryFilter(SessionRepository < S > sessionRepository) { if (sessionRepository == null) { throw new IllegalArgumentException("sessionRepository cannot be null"); } this.sessionRepository = sessionRepository; } public void setHttpSessionStrategy(HttpSessionStrategy httpSessionStrategy) { if (httpSessionStrategy == null) { throw new IllegalArgumentException("httpSessionStrategy cannot be null"); } /** 通过前面的spring-session功能介绍,我们知道spring-session可以支持单浏览器多 session, 就是通过MultiHttpSessionStrategyAdapter来实现的。 每个浏览器拥有一个sessionID,但是这个sessionID拥有多个别名(根据浏览器的tab)。如: 别名1 sessionID 别名2 sessionID ... 而这个别名通过url来传递,这就是单浏览器多session原理了 **/ this.httpSessionStrategy = new MultiHttpSessionStrategyAdapter( httpSessionStrategy); } public void setHttpSessionStrategy(MultiHttpSessionStrategy httpSessionStrategy) { if (httpSessionStrategy == null) { throw new IllegalArgumentException("httpSessionStrategy cannot be null"); } this.httpSessionStrategy = httpSessionStrategy; } /** 该方法相当于重写了doFilter,只是spring-session又做了多一层封装。 在这个方法里创建自定义的 request和response,然后传递到过滤器链filterChain **/ @Override protected void doFilterInternal(HttpServletRequest request , HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository); /** spring-session重写的ServletRequest。这个类继承了HttpServletRequestWrapper **/ SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper( request, response, this.servletContext); SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper( wrappedRequest, response); HttpServletRequest strategyRequest = this.httpSessionStrategy .wrapRequest(wrappedRequest, wrappedResponse); HttpServletResponse strategyResponse = this.httpSessionStrategy .wrapResponse(wrappedRequest, wrappedResponse); try { /** 传递自定义 request和response到链中,想象下如果 该spring-sessionFilter位于过滤器链的第一个,那么后续的Filter, 以及到达最后的控制层所获取的 request和response,是不是就是我们自定义的了? **/ filterChain.doFilter(strategyRequest, strategyResponse); } finally { wrappedRequest.commitSession(); } } public void setServletContext(ServletContext servletContext) { this.servletContext = servletContext; } /** 这个就是Servlet response的重写类了 */ private final class SessionRepositoryResponseWrapper extends OnCommittedResponseWrapper { private final SessionRepositoryRequestWrapper request; SessionRepositoryResponseWrapper(SessionRepositoryRequestWrapper request , HttpServletResponse response) { super(response); if (request == null) { throw new IllegalArgumentException("request cannot be null"); } this.request = request; } /** 这步是持久化session到存储容器,我们可能会在一个控制层里多次调用session的操作方法 如果我们每次对session的操作都持久化到存储容器,必定会带来性能的影响。比如redis 所以我们可以在整个控制层执行完毕了,response返回信息到浏览器时,才持久化session **/ @Override protected void onResponseCommitted() { this.request.commitSession(); } } /** spring-session 的request重写类,这几乎是最重要的一个重写类。里面重写了获取getSession,Session等方法以及类 */ private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper { private Boolean requestedSessionIdValid; private boolean requestedSessionInvalidated; private final HttpServletResponse response; private final ServletContext servletContext; private SessionRepositoryRequestWrapper(HttpServletRequest request , HttpServletResponse response, ServletContext servletContext) { super(request); this.response = response; this.servletContext = servletContext; } /** * Uses the HttpSessionStrategy to write the session id to the response and * persist the Session. */ private void commitSession() { HttpSessionWrapper wrappedSession = getCurrentSession(); if (wrappedSession == null) { // session失效,删除cookie或者header if (isInvalidateClientSession()) { SessionRepositoryFilter.this.httpSessionStrategy .onInvalidateSession(this, this.response); } } else { S session = wrappedSession.getSession(); SessionRepositoryFilter.this.sessionRepository.save(session); if (!isRequestedSessionIdValid() || !session.getId() .equals(getRequestedSessionId())) { // 把cookie或者header写回给浏览器保存 SessionRepositoryFilter.this.httpSessionStrategy.onNewSession(session , this, this.response); } } } @SuppressWarnings("unchecked") private HttpSessionWrapper getCurrentSession() { return (HttpSessionWrapper) getAttribute(CURRENT_SESSION_ATTR); } private void setCurrentSession(HttpSessionWrapper currentSession) { if (currentSession == null) { removeAttribute(CURRENT_SESSION_ATTR); } else { setAttribute(CURRENT_SESSION_ATTR, currentSession); } } @SuppressWarnings("unused") public String changeSessionId() { HttpSession session = getSession(false); if (session == null) { throw new IllegalStateException( "Cannot change session ID. There is no session associated with this request."); } // eagerly get session attributes in case implementation lazily loads them Map < String, Object > attrs = new HashMap < String, Object > (); Enumeration < String > iAttrNames = session.getAttributeNames(); while (iAttrNames.hasMoreElements()) { String attrName = iAttrNames.nextElement(); Object value = session.getAttribute(attrName); attrs.put(attrName, value); } SessionRepositoryFilter.this.sessionRepository.delete(session.getId()); HttpSessionWrapper original = getCurrentSession(); setCurrentSession(null); HttpSessionWrapper newSession = getSession(); original.setSession(newSession.getSession()); newSession.setMaxInactiveInterval(session.getMaxInactiveInterval()); for (Map.Entry < String, Object > attr: attrs.entrySet()) { String attrName = attr.getKey(); Object attrValue = attr.getValue(); newSession.setAttribute(attrName, attrValue); } return newSession.getId(); } // 判断session是否有效 @Override public boolean isRequestedSessionIdValid() { if (this.requestedSessionIdValid == null) { String sessionId = getRequestedSessionId(); S session = sessionId == null ? null : getSession(sessionId); return isRequestedSessionIdValid(session); } return this.requestedSessionIdValid; } private boolean isRequestedSessionIdValid(S session) { if (this.requestedSessionIdValid == null) { this.requestedSessionIdValid = session != null; } return this.requestedSessionIdValid; } private boolean isInvalidateClientSession() { return getCurrentSession() == null && this.requestedSessionInvalidated; } private S getSession(String sessionId) { // 从session存储容器中根据sessionID获取session S session = SessionRepositoryFilter.this.sessionRepository .getSession(sessionId); if (session == null) { return null; } // 设置sesison的最后访问时间,以防过期 session.setLastAccessedTime(System.currentTimeMillis()); return session; } /** 这个方法是不是很熟悉,下面还有个getSession()才更加熟悉。没错,就是在这里重新获取session方法 **/ @Override public HttpSessionWrapper getSession(boolean create) { //快速获取session,可以理解为一级缓存、二级缓存这种关系 HttpSessionWrapper currentSession = getCurrentSession(); if (currentSession != null) { return currentSession; } //从httpSessionStratge里面根据cookie或者header获取sessionID String requestedSessionId = getRequestedSessionId(); if (requestedSessionId != null && getAttribute(INVALID_SESSION_ID_ATTR) == null) { //从存储容器获取session以及设置当次初始化属性 S session = getSession(requestedSessionId); if (session != null) { this.requestedSessionIdValid = true; currentSession = new HttpSessionWrapper(session, getServletContext()); currentSession.setNew(false); setCurrentSession(currentSession); return currentSession; } else { if (SESSION_LOGGER.isDebugEnabled()) { SESSION_LOGGER.debug( "No session found by id: Caching result for getSession(false) for this HttpServletRequest."); } setAttribute(INVALID_SESSION_ID_ATTR, "true"); } } if (!create) { return null; } if (SESSION_LOGGER.isDebugEnabled()) { SESSION_LOGGER.debug( "A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for " + SESSION_LOGGER_NAME , new RuntimeException( "For debugging purposes only (not an error)")); } // 如果该浏览器或者其他http访问者是初次访问服务器,则为他创建个新的session S session = SessionRepositoryFilter.this.sessionRepository.createSession(); session.setLastAccessedTime(System.currentTimeMillis()); currentSession = new HttpSessionWrapper(session, getServletContext()); setCurrentSession(currentSession); return currentSession; } @Override public ServletContext getServletContext() { if (this.servletContext != null) { return this.servletContext; } // Servlet 3.0+ return super.getServletContext(); } @Override public HttpSessionWrapper getSession() { return getSession(true); } @Override public String getRequestedSessionId() { return SessionRepositoryFilter.this.httpSessionStrategy .getRequestedSessionId(this); } /** HttpSession的重写类 */ private final class HttpSessionWrapper extends ExpiringSessionHttpSession < S > { HttpSessionWrapper(S session, ServletContext servletContext) { super(session, servletContext); } @Override public void invalidate() { super.invalidate(); SessionRepositoryRequestWrapper.this.requestedSessionInvalidated = true; setCurrentSession(null); SessionRepositoryFilter.this.sessionRepository.delete(getId()); } } } }
Redis存储容器实现。
实现存储公共基础类->FindByIndexNameSessionRepository ,里面主要有根据indexName从redis中查找session、根据sessionID对redis中的session增删改查的方法。
redis的session存储容器,实际上的话,spring session是存在着一些缺陷的,例如,不能够做到session的过期以及销毁的实时发布事件,还有getCurrentSession当中,可能存在的一些并发问题,但是,总的来说的话,可用性还是比较的高的。
关于springsession原理的相关内容就给你介绍到这里啦,此文源于网络,仅供参考。
希望上面的文章内容可以对你的编程之路有所帮助哦!更多java编程常见问题,请继续通过奇Q工具网的内容了解并解决吧。
推荐阅读: