spring-session简介及实现原理源码分析
⼀:spring-session介绍
1.简介
session⼀直都是我们做集时需要解决的⼀个难题,过去我们可以从serlvet容器上解决,⽐如开源servlet容器-tomcat提供的tomcat-redis-session-manager、memcached-session-manager。
或者通过nginx之类的负载均衡做ip_hash,路由到特定的服务器上..
但是这两种办法都存在弊端。
spring-session是spring旗下的⼀个项⽬,把servlet容器实现的httpSession替换为spring-session,专注于解决 session管理问题。可简单快速且⽆缝的集成到我们的应⽤中。
2.⽀持功能
1)轻易把session存储到第三⽅存储容器,框架提供了redis、jvm的map、mongo、gemfire、hazelcast、jdbc等多种存储session的容器的⽅式。
2)同⼀个浏览器同⼀个⽹站,⽀持多个session问题。
3)RestfulAPI,不依赖于cookie。可通过header来传递jessionID
4)WebSocket和spring-session结合,同步⽣命周期管理。
3.集成⽅式
主要分为以下⼏个集成步骤:
1)引⼊依赖jar包
2)注解⽅式或者xml⽅式配置特定存储容器的存储⽅式,如redis的xml配置⽅式
<context:annotation-config/>
/** 初始化⼀切spring-session准备,且把springSessionFilter放⼊IOC **/
<beanclass="org.springframework.fig.annotation.web.http.RedisHttpSessionConfiguration"/>
/** 这是存储容器的链接池 **/
<beanclass="org.tion.lettuce.LettuceConnectionFactory"/>
3)xml⽅式配置 l ,配置 springSessionFilter到 filter chain中
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher><dispatcher>ERROR</dispatcher>
</filter-mapping>
⼆:spring-session框架内部剖析
1.框架⾼层抽象结构图
2.spring-session重写servlet request 及 redis实现存储相关问题
spring-session⽆缝替换应⽤服务器的request⼤概原理是:
1.⾃定义个Filter,实现doFilter⽅法
2.继承 HttpServletRequestWrapper 、HttpServletResponseWrapper 类,重写getSession等相关⽅法(在这些⽅法⾥调⽤相关的 session存储容器操作类)。
3.在第⼀步的doFilter中,new 第⼆步⾃定义的request和response的类。并把它们分别传递到过滤器链
4.把该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⾃带两个实现类
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 {
wrappedRequestmitSession();
}
}
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");
}
}
/**
这步是持久化session到存储容器,我们可能会在⼀个控制层⾥多次调⽤session的操作⽅法
如果我们每次对session的操作都持久化到存储容器,必定会带来性能的影响。⽐如redis
所以我们可以在整个控制层执⾏完毕了,response返回信息到浏览器时,才持久化session
**/
@Override
protected void onResponseCommitted() {
}
}
/**
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.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, sponse);
servlet和tomcat的关系}
}
else {
S session = Session();
SessionRepositoryFilter.this.sessionRepository.save(session);
if (!isRequestedSessionIdValid()
|| !Id().equals(getRequestedSessionId())) {
// 把cookie或者header写回给浏览器保存
SessionRepositoryFilter.NewSession(session,
this, sponse);
}
}
}
@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 = AttributeNames();
while (iAttrNames.hasMoreElements()) {
String attrName = Element();
Object value = Attribute(attrName);
attrs.put(attrName, value);
}
SessionRepositoryFilter.this.sessionRepository.Id());
HttpSessionWrapper original = getCurrentSession();
setCurrentSession(null);
HttpSessionWrapper newSession = getSession();
original.Session());
newSession.MaxInactiveInterval());
for (Map.Entry<String, Object> attr : Set()) {
String attrName = Key();
Object attrValue = Value();
newSession.setAttribute(attrName, attrValue);
}
Id();
}
// 判断session是否有效
@Override
public boolean isRequestedSessionIdValid() {
if (questedSessionIdValid == null) {
String sessionId = getRequestedSessionId();
S session = sessionId == null ? null : getSession(sessionId);
return isRequestedSessionIdValid(session);
}
questedSessionIdValid;
}
private boolean isRequestedSessionIdValid(S session) {
if (questedSessionIdValid == null) {
}
questedSessionIdValid;
}
private boolean isInvalidateClientSession() {
return getCurrentSession() == null && questedSessionInvalidated;
}
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) {
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.ateSession();
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+
ServletContext();
}
@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();
questedSessionInvalidated = true;
setCurrentSession(null);
SessionRepositoryFilter.this.sessionRepository.delete(getId());
}
}
}
}
总结
以上就是本⽂关于spring-session简介及实现原理源码分析的全部内容,希望对⼤家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不⾜之处,欢迎留⾔指出!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论