疯狂创客圈 Java 高并发【 亿级流量聊天室实战】实战系列 【博客园总入口

架构师成长+面试必备之 高并发基础书籍 【Netty Zookeeper Redis 高并发实战


前言

Crazy-SpringCloud 微服务脚手架 &视频介绍

Crazy-SpringCloud 微服务脚手架,是为 Java 微服务开发 入门者 准备的 学习和开发脚手架。并配有一系列的使用教程和视频,大致如下:

高并发 环境搭建 图文教程和演示视频,陆续上线:

中间件 链接地址
Linux Redis 安装(带视频) Linux Redis 安装(带视频)
Linux Zookeeper 安装(带视频) Linux Zookeeper 安装, 带视频
Windows Redis 安装(带视频) Windows Redis 安装(带视频)
RabbitMQ 离线安装(带视频) RabbitMQ 离线安装(带视频)
ElasticSearch 安装, 带视频 ElasticSearch 安装, 带视频
Nacos 安装(带视频) Nacos 安装(带视频)

Crazy-SpringCloud 微服务脚手架 图文教程和演示视频,陆续上线:

组件 链接地址
Eureka Eureka 入门,带视频
SpringCloud Config springcloud Config 入门,带视频
spring security spring security 原理+实战
Spring Session SpringSession 独立使用
分布式 session 基础 RedisSession (自定义)
重点: springcloud 开发脚手架 springcloud 开发脚手架
SpingSecurity + SpringSession 死磕 (写作中) SpingSecurity + SpringSession 死磕

小视频以及所需工具的百度网盘链接,请参见 疯狂创客圈 高并发社群 博客

SpringSession 独立使用 的场景和问题

当Zuul网关接收到http请求后,当请求进入对应的Filter进行过滤,通过 SpringSecurity 认证后,提取 SessionID,转发给各个微服务,通过Spring-Session创建的分布式微服务,实现Session共享!

特点:

(1)浏览器和移动端,和Nginx代理,token 是可见的,但是 session 不可见。

(2)各个微服务,用到共享Session,sessionId是可见的。

(3)各个微服务,可以通过自定义的 SessionHolder 共享类,可以静态的取得分布式Session的公共数据,比如基础的用户信息。提升编程的效率。 具体请参见 SpringCloud 开发脚手架。

具体场景的请求处理流程:

问题:

问题一:需要定制ID解析器

场景1 :如果Rest请求从Zuul 过来,Zuul 会在头部设置 sessionID,就是这个场景首先从head中去取

    String headerValue = request.getHeader(this.headerName);

场景2: 如果是 单体微服务直接访问 ,就是这个场景 SpringSecurity 会将 sessionID,放在 attribute中。这种场景,直接从从attribute中去取sessionID

  headerValue = (String) request.getAttribute(SessionConstants.SESSION_SEED);

SpringSession自带的 ID解析器 ,不能满足要求,需要重新定制一个。关于ID解析器,请参见 疯狂创客圈 的另一博文 SpringSession自带的 ID解析器 最全解读

问题二:需要定制sessionRepository 存储器

sessionRepository 负责存储 session 到Redis,需要修改模式为立即提交,以免setAttribute的属性,不能及时写入Redis,这是笔者调试了几个小时发现的坑

问题三:需要定制SessionRepositoryFilter 过滤器

将Session请求,保持到 SessionHolder 的 ThreadLocal 本地变量中,方便统一获取,方便编程。例如:

SessionHolder.getSessionUser().getLoginName());

直接从redissession,读取用户的名称,多方便呀。

总之: 使用集成的默认的SpringSession ,没有办法深入的解决问题。 有两种方法

  • 第一种是自制 分布式 Session。

具体请参考 疯狂创客圈 博客 分布式RedisSession 自制

这种方法的优点:简陋。 缺点:过于简陋。

在流程和思想上,和第下面的第二种是类似的,可供学习使用,方便理解。

  • 第二种是 SpringSession 独立使用。

就是本文的内容。

说明: 第二种在流程和思想上第一种是类似的,可供学习使用,方便理解,建议先了解第一种,第二种就好掌握多了

理论基础: springSession 原理

spring-session分为以下核心模块:

  • 过滤器 SessionRepositoryFilter:Servlet规范中Filter的实现,用 Spring Session 替换原来的 HttpSession,具体的方式是使用了自己的两个包装器: HttpServletRequest 和HttpServletResponse。

  • 包装器 HttpServerletRequest/HttpServletResponse/HttpSessionWrapper:包装原有的HttpServletRequest、HttpServletResponse和Spring Session,实现切换Session和透明继承HttpSession的关键之所在

  • Session:Spring Session模块

  • 存储器 SessionRepository:负责 Spring Session的存储

具体见下图:

Spring Session模块

spring-session中则抽象出单独的Session层接口,让后再使用适配器模式将Session适配层Servlet规范中的HttpSession。

类图如下:

RedisSession 的本质

内部封装一个 MapSession,MapSession 本质是一个 map。而 RedisSession 的主要职责:负责 MapSession中 Map 的K-V内容的 Redis 存储。

spring-session 原理,请参见博文

第1步: ID解析器 自定义

场景1 :如果Rest请求从Zuul 过来,Zuul 会在头部设置 sessionID,就是这个场景首先从head中去取

    String headerValue = request.getHeader(this.headerName);

场景2: 如果是 单体微服务直接访问 ,就是这个场景 SpringSecurity 会将 sessionID,放在 attribute中。这种场景,直接从从attribute中去取sessionID

  headerValue = (String) request.getAttribute(SessionConstants.SESSION_SEED);

实现 HttpSessionIdResolver 接口,定义一个完整的ID解析器,代码如下:

package com.crazymaker.springcloud.standard.config;

//...省略import

@Data
public class CustomedSessionIdResolver implements HttpSessionIdResolver { private RedisTemplate<Object, Object> redisTemplet = null; private static final String HEADER_AUTHENTICATION_INFO = "Authentication-Info"; private final String headerName; /**
* The name of the header to obtain the session id from.
*
*/
public CustomedSessionIdResolver() { //设置 head头的名称
this.headerName = SessionConstants.SESSION_SEED;
if (headerName == null) {
throw new IllegalArgumentException("headerName cannot be null");
}
} @Override
public List<String> resolveSessionIds(HttpServletRequest request) {
//step1:首先从head中去取sessionID
// 如果从Zuul 过来,就是这个场景
String headerValue = request.getHeader(this.headerName); //step1:首先从attribute中去取sessionID
// 如果是 单体微服务直接访问 ,就是这个场景
//SpringSecurity 会将 sessionID,放在 attribute中
if (StringUtils.isEmpty(headerValue)) {
headerValue = (String) request.getAttribute(SessionConstants.SESSION_SEED);
if (!StringUtils.isEmpty(headerValue)) { headerValue = SessionConstants.getRedisSessionID(headerValue); }
} return (headerValue != null) ?
Collections.singletonList(headerValue) : Collections.emptyList();
} @Override
public void setSessionId(HttpServletRequest request, HttpServletResponse response,
String sessionId) {
//不需要返回sessionId
//到前端
response.setHeader(this.headerName, "");
// response.setHeader(this.headerName, sessionId);
} @Override
public void expireSession(HttpServletRequest request, HttpServletResponse response) {
response.setHeader(this.headerName, "");
} //....省略其他
}

第2步:自定义一个SessionRepositoryFilter

这一步,不是必须的。

主要作用: 在过滤器的处理方法 doFilterInternal(....), 要将 redis session 保存到 SessionHolder 类中,方便后面访问。代码如下:

    SessionHolder.setRequest(wrappedRequest);
SessionHolder.setSession(wrappedRequest.getSession());

复制源码中的 SessionRepositoryFilter 类,改名为 CustomedSessionRepositoryFilter, 简单的修改一下,代码如下:

package com.crazymaker.springcloud.standard.security.filter;
//.....
public class CustomedSessionRepositoryFilter<S extends Session> extends OncePerRequestFilter { private static final String SESSION_LOGGER_NAME = CustomedSessionRepositoryFilter.class
.getName().concat(".SESSION_LOGGER"); //.... //默认的ID解析器,需要替换掉
private HttpSessionIdResolver httpSessionIdResolver = new CookieHttpSessionIdResolver(); /**
* Creates a new instance.
*
* @param sessionRepository the <code>SessionRepository</code> to use. Cannot be null.
*/
public CustomedSessionRepositoryFilter(SessionRepository<S> sessionRepository) {
if (sessionRepository == null) {
throw new IllegalArgumentException("sessionRepository cannot be null");
}
this.sessionRepository = sessionRepository;
} /**
* Sets the {@link HttpSessionIdResolver} to be used. The default is a
* {@link CookieHttpSessionIdResolver}.
*
* @param httpSessionIdResolver the {@link HttpSessionIdResolver} to use. Cannot be
* null.
*/
public void setHttpSessionIdResolver(HttpSessionIdResolver httpSessionIdResolver) {
if (httpSessionIdResolver == null) {
throw new IllegalArgumentException("httpSessionIdResolver cannot be null");
}
this.httpSessionIdResolver = httpSessionIdResolver;
} @Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository); if(this.servletContext==null)
{
this.servletContext=request.getServletContext();
} SessionRepositoryRequestWrapper wrappedRequest =
new SessionRepositoryRequestWrapper(request, response, this.servletContext);
SessionRepositoryResponseWrapper wrappedResponse =
new SessionRepositoryResponseWrapper(wrappedRequest, response); /**
* 将Session请求,保持到 SessionHolder 的 ThreadLocal 本地变量中,方便统一获取
*/
SessionHolder.setRequest(wrappedRequest);
SessionHolder.setSession(wrappedRequest.getSession()); try {
filterChain.doFilter(wrappedRequest, wrappedResponse);
} finally {
wrappedRequest.commitSession();
}
} public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
} /**
* Allows ensuring that the session is saved if the response is committed.
*
* @author Rob Winch
* @since 1.0
*/
private final class SessionRepositoryResponseWrapper
extends OnCommittedResponseWrapper { //.....
} /**
* A {@link javax.servlet.http.HttpServletRequest} that retrieves the
* {@link javax.servlet.http.HttpSession} using a
* {@link org.springframework.session.SessionRepository}.
*
* @author Rob Winch
* @since 1.0
*/
private final class SessionRepositoryRequestWrapper
extends HttpServletRequestWrapper { //.... } static class HttpSessionAdapter<S extends Session> implements HttpSession { //.... } }

第3步:自动配置 Configuration 的定制

简单粗暴,将springsession 默认的自动配置,废掉了。

复制一份 RedisHttpSessionConfiguration, 名字叫做 CustomedRedisHttpSessionConfiguration ,主要作用:

(1) 创建 CustomedSessionIdResolver ID解析器的IOC Bean

(2) 创建 sessionRepository 保存器 的IOC Bean时,修改模式为立即提交

package com.crazymaker.springcloud.standard.config;

//....

@Configuration
@EnableScheduling
public class CustomedRedisHttpSessionConfiguration
implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware,
SchedulingConfigurer { static final String DEFAULT_CLEANUP_CRON = "0 * * * * *"; //...... @DependsOn("httpSessionIdResolver")
@Bean
public RedisOperationsSessionRepository sessionRepository(CustomedSessionIdResolver httpSessionIdResolver) {
RedisTemplate<Object, Object> redisTemplate = createRedisTemplate();
RedisOperationsSessionRepository sessionRepository =
new RedisOperationsSessionRepository(redisTemplate); sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher);
if (this.defaultRedisSerializer != null) {
sessionRepository.setDefaultSerializer(this.defaultRedisSerializer);
}
sessionRepository
.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
if (StringUtils.hasText(this.redisNamespace)) {
sessionRepository.setRedisKeyNamespace(this.redisNamespace+":"+SessionConstants.REDIS_SESSION_KEY_PREFIX);
}
//修改模式为立即提交
sessionRepository.setRedisFlushMode(RedisFlushMode.IMMEDIATE);
// sessionRepository.setRedisFlushMode(this.redisFlushMode);
int database = resolveDatabase();
sessionRepository.setDatabase(database); httpSessionIdResolver.setRedisTemplet(redisTemplate); this.sessionRepository = sessionRepository;
return sessionRepository;
}
//.... /**
* 配置 ID 解析器,从 header 解析id
*
* @return
*/
@Bean("httpSessionIdResolver")
public CustomedSessionIdResolver httpSessionIdResolver() {
return new CustomedSessionIdResolver(SessionConstants.SESSION_ID);
} }

第4步: 在SpringSecurityConfig中,使用过滤器

package com.crazymaker.springcloud.user.info.config;

//....

import javax.annotation.Resource;
import java.util.Arrays; @EnableWebSecurity()
public class UserProviderWebSecurityConfig extends WebSecurityConfigurerAdapter { @Resource
private UserLoginService userLoginService; protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers(
"/v2/api-docs",
"/swagger-resources/configuration/ui",
"/swagger-resources",
"/swagger-resources/configuration/security",
"/swagger-ui.html",
"/api/user/login/v1",
.permitAll()
.anyRequest().authenticated() .and() .formLogin().disable()
.sessionManagement().disable()
.cors()
.and()
.addFilterAfter(new OptionsRequestFilter(), CorsFilter.class)
.apply(new JsonLoginConfigurer<>()).loginSuccessHandler(jsonLoginSuccessHandler())
.and()
.apply(new JwtLoginConfigurer<>()).tokenValidSuccessHandler(jwtRefreshSuccessHandler()).permissiveRequestUrls("/logout")
.and()
.logout()
.addLogoutHandler(tokenClearLogoutHandler())
.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler())
.and()
.addFilterBefore(springSessionRepositoryFilter(), SessionManagementFilter.class)
.sessionManagement().disable()
; } @Resource
RedisOperationsSessionRepository sessionRepository; @Resource
public CustomedSessionIdResolver httpSessionIdResolver; @DependsOn({"sessionRepository","httpSessionIdResolver"})
@Bean("jwtAuthenticationProvider")
protected AuthenticationProvider jwtAuthenticationProvider() {
return new JwtAuthenticationProvider(sessionRepository,httpSessionIdResolver);
} //.... }

具体,请关注 Java 高并发研习社群博客园 总入口


最后,介绍一下疯狂创客圈:疯狂创客圈,一个Java 高并发研习社群博客园 总入口

疯狂创客圈,倾力推出:面试必备 + 面试必备 + 面试必备 的基础原理+实战 书籍 《Netty Zookeeper Redis 高并发实战


疯狂创客圈 Java 死磕系列

  • Java (Netty) 聊天程序【 亿级流量】实战 开源项目实战

Java 面试题 一网打尽**


SpringSession 独立使用的更多相关文章

  1. SpringSession header/cookie/attribute存放 session id

    SpringSession header/cookie/attribute存放 SessionID(死磕) 疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长 ...

  2. SpringBoot SpringCloud 热部署 热加载 热调试

    疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Redis 高并发实战 ] Crazy-Sp ...

  3. Eureka 入门,带视频

    疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Redis 高并发实战 ] 前言 Crazy ...

  4. springcloud Config 入门,带视频

    疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Redis 高并发实战 ] 前言 Crazy ...

  5. spring security 原理+实战

    疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Redis 高并发实战 ] 前言 Crazy ...

  6. RedisSession (自定义)

    RedisSession (自定义) 疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Red ...

  7. SpringCloud 脚手架

    疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Redis 高并发实战 ] 前言 Crazy ...

  8. SpringCloud 亿级流量 架构演进

    疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Redis 高并发实战 ] 前言 Crazy ...

  9. Zuul 详解,带视频

    疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Redis 高并发实战 ] 前言 Crazy ...

随机推荐

  1. 关于小码哥kylin

    技术格言: 用双手改变人生,用代码改变世界! 个人网站:http://www.isainttech.com QQ:56619556 Email:dragonsaint@qq.com 微信:kylin1 ...

  2. vue中的$EventBus.$emit、$on 遇到的问题

    今天在项目中遇到的一个需求: 在一个选项卡功能的页面,出现的问题是,当点击选项卡的某个选项时,会同时加载整个选项卡的数据,本身产品就很大,数据很多,所以这个问题无法忽略: 仔细研究下发现,当刚进入页面 ...

  3. 超好用的自带火焰图的 Java 性能分析工具 Async-profiler 了解一下

    如果你经常遇到 Java 线上性能问题束手无策,看着线上服务 CPU 飙升一筹莫展,发现内存不断泄露满脸茫然.别慌,这里有一款低开销.自带火焰图.让你大呼好用的 Java 性能分析工具 - async ...

  4. windows系统tomcat上开发的j2ee程序,如何适配linux系统上奔跑的websphere7

    公司需要将几个windows系统tomcat中间件下开发的j2ee系统部署到linux系统websphere7中间件下去运行. 这就需要做系统的适配工作.由于时间比较久了,具体问题就不详细写了.把这个 ...

  5. shell脚本exercise2

    通过文件里面的网址,判断是否访问成功网址 #!/bin/bash check(){ code=`curl -I -m -o /dev/null -s -w %{http_code} http://$u ...

  6. 洛谷 P1920 成功密码 题解

    这是蒟蒻的第一篇题解,(之前的都没过,估计这篇也过不了 回到正题 这题,本蒟蒻第一眼看到以后,就决定咦,这不是模拟吗? 看到世界范围,嗯,打扰了. 扯回正题 首先,暴力肯定是A不了的(至少我A不了 但 ...

  7. mysql主从复制原理及实践

    Mysql主从复制原理及实践 mysql主从框架       MySQL主从架构是MySQL集群中最基本也是最常用的一种架构部署,能够满足很多业务需求,常见的有一主一从或者一主多从.可以防止单一主机的 ...

  8. 第6节:Java基础 - 三大集合(上)

    第6节:Java基础 - 三大集合(上) 本小节是Java基础篇章的第四小节,主要介绍Java中的常用集合知识点,涉及到的内容包括Java中的三大集合的引出,以及HashMap,Hashtable和C ...

  9. BZOJ 3033 太鼓达人(DFS+欧拉回路)

    Description 七夕祭上,Vani牵着cl的手,在明亮的灯光和欢乐的气氛中愉快地穿行.这时,在前面忽然出现了一台太鼓达人机台,而在机台前坐着的是刚刚被精英队伍成员XLk.Poet_shy和ly ...

  10. POJ 3660 cow contest (Folyed 求传递闭包)

    N (1 ≤ N ≤ 100) cows, conveniently numbered 1..N, are participating in a programming contest. As we ...