Spring中自定义Session管理,Spring Session源码解析
Posted 香菜+
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring中自定义Session管理,Spring Session源码解析相关的知识,希望对你有一定的参考价值。
系列文章:Spring Boot学习大纲,可以留言自己想了解的技术点
目录
系列文章:Spring Boot学习大纲,可以留言自己想了解的技术点
4、自定义一个Session的存储和构造,替换掉tomcat的session机制
4.2.3>创建一个session的容器,这里可以换成你想要的
5.2 开启配置@EnableRedisHttpSession
1、session是什么?
session用中文翻译就是会话,主要是为了维护web访问时的上下文信息保存,避免出现说完话就忘了对方是谁的情况
1>session在哪里?
session存储在服务器的内容中
2>服务器怎么知道每次说话的是哪个session
http访问的时候,header中有一个属性是sessionId,服务器根据session查找当前存在的属性
以chrome浏览器为例,访问一个基于tomcat服务器的网站的时候,
浏览器第一次访问服务器,服务器会在响应头添加Set-Cookie:“JSESSIONID=XXXXXXX”信息,要求客户端设置cooki
3>session的使用
// 获取session对象
HttpSession session = request.getSession();
// 获取
Object key = session.getAttribute("key");
session.setAttribute("key","value");
2、session的数据结构
session的数据结构没有固定的,怎么实现是看实现方式,这里介绍下常规认识的tomcat中的session数据结构
在tomcat中,session的数据结构是ConcurrentMap,key 是属性名,value是属性值
参考:org.apache.catalina.session.StandardSession
protected ConcurrentMap<String, Object> attributes = new ConcurrentHashMap();
后面我们要自定义一个session的存储方式。
3、tomcat中的session
3.1 session的创建时机和同步
客户端第一次请求request.getsession()时,也就是说客户端的请求中服务端第一次调用request.getsession()时,服务器会创建了session对象并保存在servlet容器的session集合中,同时生成一个sessionId,
向客户端发送要求设置cookie的响应(cookie中设置session id信息),客户端收到响应后,在客户端设置了一个jsessionid=xxxxxxx的cookie信息;
接下来客户端每次向服务器发送请求时,请求头都会带上该cookie信息(包含session id),那么之后的每次请求都能从servlet容器的session集合中找到客户端对应的session了,这样也就相当于保持了用户与服务器的交互状态。
3.2 session调用堆栈
调试代码:
@GetMapping("/test/id")
public String test(@PathVariable(value = "id") String id, HttpServletRequest request)
HttpSession session = request.getSession();
Long currentTime = timeService.getCurrentTime(id);
System.out.println("参数 : "+ id +" result "+ currentTime);
return "Hello";
看下断点的位置
3.3 源码分析
直接跟代码
HttpSession session = request.getSession();
下面流程画下来了,建议有条件的话跟一下代码,大概了解下
4、自定义一个Session的存储和构造,替换掉tomcat的session机制
4.1 思路
先看下tomcat整个数据的流程
思路1:直接替换掉tomcat的contextManger ,复杂了点,而且不适合Springboot,因为tomcat是内嵌的,如果换了web容器不一定适合
思路2:直接替换掉servlet中的session实现,在刚进入web容器的时候直接替换掉session实现,也就是在filter处
4.2 自定义session创建和管理
这里采用方案2,创建一个 filter,然后在入口处直接替换掉 request 中session的实现,直接上代码吧
4.2.1>创建一个springboot项目
不多说,一路next
4.2.2> 创建一个自定义的session
package com.xin.sessiontest.customize;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionContext;
import java.util.*;
public class CustomizeSession implements HttpSession
private final Map<String, Object> attributeMap = new LinkedHashMap<>();
private boolean isNew;
private String id;
public CustomizeSession(String id)
if (id == null || "".equals(id.trim()))
id = UUID.randomUUID().toString().replace("-", "");
this.id = id;
this.isNew = true;
System.out.println("新session id : " + this.id);
public long getCreationTime()
return 0;
public String getId()
return this.id;
public long getLastAccessedTime()
return 0;
public ServletContext getServletContext()
return null;
public void setMaxInactiveInterval(int interval)
public int getMaxInactiveInterval()
return 0;
public HttpSessionContext getSessionContext()
return null;
public Object getAttribute(String name)
return this.attributeMap.get(name);
public Object getValue(String name)
return null;
public Enumeration getAttributeNames()
System.out.println("CustomizeSession的getAttributeNames方法");
return Collections.enumeration(this.attributeMap.keySet());
public String[] getValueNames()
return new String[0];
public void setAttribute(String name, Object value)
this.attributeMap.put(name, value);
public void putValue(String name, Object value)
public void removeAttribute(String name)
public void removeValue(String name)
public void invalidate()
public boolean isNew()
return this.isNew;
public void setIsNew(boolean isNew)
this.isNew = isNew;
4.2.3>创建一个session的容器,这里可以换成你想要的
key 是sessionId,value是session
package com.xin.sessiontest.customize;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class CustomizeSessionContainer
public static final String DEFAULT_SESSION_ID_NAME = "JSESSIONID";
private static final Map<String, CustomizeSession> sessionMap = new ConcurrentHashMap<>();
public static CustomizeSession getSession(String sessionId, HttpServletResponse response)
return getSession(sessionId, true, response);
public static CustomizeSession getSession(String sessionId, boolean create, HttpServletResponse response)
if (sessionId == null)
sessionId = "";
CustomizeSession session = sessionMap.get(sessionId);
if (session != null)
session.setIsNew(false);
return session;
if (create)
session = new CustomizeSession(sessionId);
sessionMap.put(session.getId(), session);
response.addCookie(new Cookie(CustomizeSessionContainer.DEFAULT_SESSION_ID_NAME,
session.getId()));
return session;
4.2.4 替换servlet的实现
package com.xin.sessiontest.customize;
import javax.servlet.http.*;
public class CustomizeHttpServletRequest extends HttpServletRequestWrapper
private HttpServletResponse response;
public CustomizeHttpServletRequest(HttpServletRequest request, HttpServletResponse response)
super(request);
this.response = response;
@Override
public HttpSession getSession()
return this.getSession(true);
@Override
public HttpSession getSession(boolean create)
Cookie[] cookies = this.getCookies();
String sessionId = "";
if (cookies != null)
for (Cookie cookie : cookies)
if (CustomizeSessionContainer.DEFAULT_SESSION_ID_NAME.equals(cookie.getName()))
sessionId = cookie.getValue();
break;
HttpSession customizeSession = CustomizeSessionContainer.getSession(sessionId, create, response);
return customizeSession;
4.2.5 创建filter
package com.xin.sessiontest.filter;
import com.xin.sessiontest.customize.CustomizeHttpServletRequest;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
@WebFilter(urlPatterns = "/*", filterName = "FirstFilter")
public class CustomizeSessionFilter implements Filter
public void init(FilterConfig filterConfig) throws ServletException
System.out.println("CustomizeSessionFilter init");
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException
CustomizeHttpServletRequest request = new CustomizeHttpServletRequest((HttpServletRequest) req, (HttpServletResponse) resp);
chain.doFilter(request, resp);
public void destroy()
System.out.println("CustomizeSessionFilter destroy");
4.2.6 测试controller
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@RestController
public class TestController
@GetMapping("/")
public String test(HttpServletRequest request)
HttpSession session = request.getSession();
session.setAttribute("key","value");
return "hello";
4.2.7 测试一下
看到后台是有打印session创建,证明已经接管了
5、Spring-session配置
Spring Session 它提供一组 API 和实现, 用于管理用户的 session 信息, 专注于解决 session 管理问题,轻易把session存储到第三方存储容器,框架提供了redis、jvm的map、mongo、gemfire、hazelcast、jdbc等多种存储session的容器的方式。主要解决分布式session共享问题
5.1 创建springboot项目,
勾选Spring Session 和Spring Data Redis
或者直接贴进去依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
5.2 开启配置@EnableRedisHttpSession
@SpringBootApplication
@EnableRedisHttpSession
public class SessionTestApplication
public static void main(String[] args)
SpringApplication.run(SessionTestApplication.class, args);
5.3 配置文件
spring:
# 选择redis为session存储
session:
store-type: redis
# 配置redis
redis:
host: 172.26.1.152
port: 6379
database: 9
# springsSession过期时间
server:
servlet:
session:
timeout: 30m
5.4 测试
@RestController
public class TestController
@GetMapping("/")
public String test(HttpServletRequest request)
HttpSession session = request.getSession();
session.setAttribute("caraway","香菜");
return "hello";
5.5 确认数据存储到redis
配置到9号DB,这里可以看到有一个key :caraway
6、Spring Session 源码分析
@EnableRedisHttpSession 导入了一个RedisHttpSessionConfiguration.class配置,
这个配置首先首先添加了一个组件RedisOperationsSessionRepository,redis操作session的dao其内部所有的方法都是增删改查的
看下百度到的图,貌似和我们实现的很像
6.1 SessionRepositoryFilter
用来切换HttpSession至Spring Session,包装HttpServletRequest和HttpServletResponse
@Order(-2147483598)
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException
request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
SessionRepositoryFilter<S>.SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request, response);
SessionRepositoryFilter<S>.SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest, response);
try
filterChain.doFilter(wrappedRequest, wrappedResponse);
finally
wrappedRequest.commitSession();
看下重点:
1、order注解,是int的最小值,这个保证所有的请求第一个经过这个filter
2、doFilterInternal 包装request 和response
6.2 SessionRepository
看下实现类,看起来实现没啥,创建一个session,保存session数据,查找session,都是常规操作
public class RedisSessionRepository implements SessionRepository<RedisSession>
public RedisSession createSession()
MapSession cached = new MapSession();
cached.setMaxInactiveInterval(this.defaultMaxInactiveInterval);
RedisSession session = new RedisSession(cached, true);
session.flushIfRequired();
return session;
public void save(RedisSession session)
if (!session.isNew)
String key = this.getSessionKey(session.hasChangedSessionId() ? session.originalSessionId : session.getId());
Boolean sessionExists = this.sessionRedisOperations.hasKey(key);
if (sessionExists == null || !sessionExists)
throw new IllegalStateException("Session was invalidated");
session.save();
public RedisSession findById(String sessionId)
String key = this.getSessionKey(sessionId);
Map<String, Object> entries = this.sessionRedisOperations.opsForHash().entries(key);
if (entries.isEmpty())
return null;
else
MapSession session = (new RedisSessionMapper(sessionId)).apply(entries);
if (session.isExpired())
this.deleteById(sessionId);
return null;
else
return new RedisSession(session, false);
7、总结
全篇写了很多,简单来说就是session的管理,从web项目中接管session。
自定义session管理和Spring session 都是相同的原理
嵌入Filter,替换Request,自定义Session容器
源码下载地址:https://download.csdn.net/download/perfect2011/87472028
最后的最后求一个免费的赞同,爱心发电
以上是关于Spring中自定义Session管理,Spring Session源码解析的主要内容,如果未能解决你的问题,请参考以下文章
Spring Boot + Swagger + 自定义 swagger-ui.html