困惑:发现一个请求跳到模板页面,model中之添加一个obj1,该页面就直接可以使用session来获取用户信息是否登陆 知识点:模板引擎存在作用域和jsp一样都有内置对象和语法,等登陆后创建session,服务端就会将该次请求的响应加上cookie并设置上session的id,困惑的原因是 springSeesion包装了原有的session和request和response,会将session的创建接管并保存自己实现的方法所保存的地方比如redis中,当任意请求携带了cookie服务端就会尝试使用该创建的session会从指定的地方获取session,如原生的cookie中的sessionId到模板引擎中也会获取到,因为只要会话就会就存在唯一会话标识,模板引擎可以直接使用这些作用域如request、session、response。
session用于服务器和浏览器在建立会话后创建的一个唯一凭证,当多个用户访问同一个服务,就会创建多个session在服务器端保存,这些session通过sessionManager来管理,每个session相当于一个map可以设置多个值存储当前浏览器会话设置的信息,session创建后会返回给浏览器唯一凭证sessionId,它会保存在cookier当中的请求路径域名下,以JSESSION为key保存sessionid值,当下次请时会携带cookie下的指定地址下cookie的数据到服务器中查询session是否存在,存在判断是否登陆并获取登陆的用户信息,当浏览器关闭后就cookie就会失效,用户重新登陆继续会话,服务端的session默认保留30分钟,当你不适用上次的seeion后会30分钟会销毁,这个时间可以在web.xml中设置时间,主动失效要么服务器关闭要么使用invalidate()才会失效,或者等带指定的时间失效,sessionManager帮我们管理,session会默认在指定的域名下工作,跨域名共享数据就会失败。
这些在单体项目中跨请求地址可以获取共享的数据,但在多体项目携带session到其他服务中就会完全失效。seesion工作原理就好比办理银行卡,第一次使用身份证建立会话,银行返回给你一个卡号,你下次拿着卡号,银行直接可以根据卡号和当前保存的卡号系统就能直接工作,卡号就是存在浏览器cookie中的sessionId,卡号系统就是管理的session,每个人都可以申请到自己的session,根据seesion直接会话。
解决方案:1多个微服务对session同步复制,不建议采取因为复制开销大网络带宽受影响 2使用一致性hash对ip或者域名hash获取整数然后再根据相同服务的数量取余操作,那么请求负载到只有一个相同的服务,当水平扩容的时候不一致,session也会有失效时间不会有所影响 3使用公共存储介质,将共享数据存储在mysql/redis中,当跨服务时可以直接让当前服务查询mysq或redis存的session信息,后端统一存储session比较安全,容易扩展,缺点有网络交互连接redis。总结解决方案可以使用一致性hash或后端统一处理,一致性hash比较麻烦,支持第二种方案。
统一存储session解决方案,spring提供springSession框架,session共享问题解决-不同服务,子域名session共享,子域session共享,如a.bowen.com获得session后可以在请求页看到cookie保存的信息,在b.bowen.com看不到上一个保存的cookie信息,要共享a的cookie就要修改a的作用域domain的地址比如将的实体作用域a,bowen.com修改为.bowen.com在b.bowen.com地址页检查cookie就会存在相同的cookie信息。那么浏览器就能跨不同的域名共享这个session,担是不能在浏览器中修改,在服务器第一次发放session时就应该扩大session的作用域,让浏览器端不同域名下符合cookie匹配的路径都能共享cookie,子域发放的seesion所以的父域名都可以使用。
httpServletResponse.addCookie(new Cookie("JSESSION",data).setDomain(" ")) spriingSession
实现的方式如此,自己添加较为麻烦。这也客户端的cookie就能共享,请求服务携带上cookie去redis查询数据。
整合springsession引入依赖:
<!-- 整合springsession -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
配置文件配置属性值,启动类开启@EnableRedisHttpSession功能。
SpringSession自动注入springSessionRepositoryFilter 它会把原生的session实现替换为自定义的session实现,相当于spring实现了自定义session功能来用redis存储。也就是在controller的请求方法的请求参数中自动封装HttpSession session的时候,当前处理器自动封装的是spring家实现的session,在使用上与原有的session的api使用相同,springSession会把session存储到redis中,而且会扩展cookio的作用域使得所有的父作用域都能共享子域的cookie,添加规则配置类、如返回自定义cookie内容,存储到session的序列化方式。
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
//放大作用域
cookieSerializer.setDomainName("gulimall.com");
cookieSerializer.setCookieName("GULISESSION");
return cookieSerializer;
}
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
这些SpringSession的使用对原有的Httpsession完全没有感知,只会额外添加了规则配置类,如自定义cookie的名字以及域名作用范围,它会代替原有的session并把它存在redis中而不是服务器上。
在redis中的存储形式如图,使用hashmap存储:
这里对于redis的数据结构要清楚知道可以应用的场景,如上map用于分布式session就是一个数据结构的例子,于此相关的redis数据结构的应用有很多,比如redisson的分步式锁,所实现锁功能的结构的使用,可以总结一下他们同样实现了java的lock接口,把锁信息维护到了redis中,至于是什么数据结构可以自己查看,比如互斥锁,读写锁,信号量等并把存储的图片放在这里,方面对redis的学习和总结。
总结:SpringSession的核心原理
1.@EnableRedisHttpSession给容器中导入了哪些组件
给容器中导入了RedisHttpSessionConfiguration这个配置通过@Import
这个RedisHttpSessionConfiguration还继承了一个SpringHttpSessionConfiguration
这个SpringHttpSessionConfiguration还是一个配置,
这个SpringHttpSessionConfiguration给容器中放了哪些内容?
1.1.首先第一个,它先初始化@PostConstruct,其意思就是这个SpringHttpSessionConfiguration类只要一构造起来
那么构造器就会初始化标注的@PostConstruct下的方法
1.2.第二个,还放了一个SessionRepositoryFilter,这个是过滤器,这个过滤器里面有session的id解析器等
这个过滤器的作用,就是每一个请求都得来过滤,也就是之前所学的HTTP的Filter
2. RedisHttpSessionConfiguration这个配置又做了什么事情?
2.1:给容器中添加了RedisIndexedSessionRepository这个组件
2.1.1:就是redis操作session,相当于是给session的增删改查都是使用redis来操作,同样如果选用mongo也会有对mongo存储数据的增删改查,比如getSession(sessionId)/delete(sessionId)等功能。springSession的核心是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 SessionRepositoryFilter.SessionRepositoryRequestWrapper(request, response);
SessionRepositoryFilter.SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryFilter.SessionRepositoryResponseWrapper(wrappedRequest, response);
try {
filterChain.doFilter(wrappedRequest, wrappedResponse);
} finally {
wrappedRequest.commitSession();
}
}
请求来的时候先执行该方法,会把SpringSession有关对redis中的session增删改查的工具类放在request域中如request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);然后把原始的请求的request、reponse做了包装,这是典型的装饰者模式,它是把原有属性对象给这个包装类,并同时该类继承或实现了该接口重写了getSession方法而不改变原始默认的实现和调用,当调用的时候是包装器重写的实现方法如该request实现的就是HttpServletRequest接口,并继承ServletRequestWrapper通过它下面的ServletRequest接口接收请求实例并交给包装者管理,同样请求实例可用使用过滤器处理让包装类来接收,就可以调用自己的getSeesion方法。SessionRepositoryRequestWrappe类的实现getSession方法它继承了HttpServletRequestWrapper类该类实现HttpServletRequest接口。构造方法中当前属性接收response对象,超类的属性接收request对向。
public SessionRepositoryFilter<S>.SessionRepositoryRequestWrapper.HttpSessionWrapper getSession() {
return this.getSession(true);
}
过滤器的过滤器链放行的是包装后的请求响应,包装后的对象应用到了后面的整个执行链,然后遵循以前的原理,来到了contriller的方法上,使用原生的HttpSession接收,由于原始的session需要request.getSession()获取并转递给HttpSession变量,我们把它包装成SessionRepositoryRequestWrapper和SessionRepositoryResponseWrapper类,以后我们获取原生的session就是调用SessionRepositoryRequestWrapper的getSession()它有自定义的实现,那么对于原生的获取就转为自定义的session获取,就是从redis中获取我们的session,它是创建session和保存session的过程我们就可以把创建的session保存到redis中而不是服务器上下次获取同样是包装器的逻辑,优先从redis获取session对象,同样HttpSession对象被指定的请求包装器创建,它也会经过包装,对session的操作如Invalidate的方法会被重写调用操作redis中的session增删改查的工具类RedisIndexedSessionRepository完成原始session的操作,用户而没有感知(包装加组合),SessionRepositoryRequestWrappe下的getSession会调用 getSession(boolean create) 重载的方法来创建原来的session对象,并利用redis的数据增删改查工具类RedisIndexedSessionRepository写入到redis中,这是创建方式,对于session本身具有的方法,也会有包装类调用RedisIndexedSessionRepository来删除session。总结就是getSession完全用到自己实现的方法,getSession是从RedisIndexedSessionRepository sessionRepository获取到的
内容比较难懂总结就是包装类继承了HttpServletRequest,包装类自己实现了getSession方法提供创建session的逻辑,并把包装的session类实例传给接口HttpSession,通过SessionRepository接口的实现对redis数据操作,它的实现者可以是mongoDB等其他数据库。
细节方面完全可以使用debug方式来阅读源码,这些才是学源码最好的方式,而不是仅仅用来排查错误,springSession设置了session过期时间但它拥有延期的功能,只要浏览器不关闭session会一直有效。
问题:SpringSession放大作用域,只能对一级扩展域名,如果多个服务所在的域名完全不同,就不能使用springSession来在多个服务之间共享session,这个时候只能使用单点登陆,在一个地方登陆其他任何的服务都能自动登陆。