系列文章:Spring Boot学习大纲,可以留言自己想了解的技术点
目录
系列文章:Spring Boot学习大纲,可以留言自己想了解的技术点
1、session是什么?
1>session在哪里?
2>服务器怎么知道每次说话的是哪个session
3>session的使用
2、session的数据结构
3、tomcat中的session
3.1 session的创建时机和同步
3.2 session调用堆栈
3.3 源码分析
4、自定义一个Session的存储和构造,替换掉tomcat的session机制
4.1 思路
4.2 自定义session创建和管理
4.2.1>创建一个springboot项目
4.2.2> 创建一个自定义的session
4.2.3>创建一个session的容器,这里可以换成你想要的
4.2.4 替换servlet的实现
4.2.5 创建filter
4.2.6 测试controller
4.2.7 测试一下
5、Spring-session配置
5.1 创建springboot项目,
5.2 开启配置@EnableRedisHttpSession
5.3 配置文件
5.4 测试
5.5 确认数据存储到redis
6、Spring Session 源码分析
6.1 SessionRepositoryFilter
6.2 SessionRepository
7、总结
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;}@Overridepublic HttpSession getSession() {return this.getSession(true);}@Overridepublic 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 测试一下
http://localhost:8080/
看到后台是有打印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# 配置redisredis:host: 172.26.1.152port: 6379database: 9# springsSession过期时间
server:servlet:session:timeout: 30m
5.4 测试
http://localhost:8080/
@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
最后的最后求一个免费的赞同,爱心发电