摘要:本文主要研究 基于 spring-seesion 解决分布式 session 的共享问题。首先讲述 session 共享问题的产生背景以及常见的解决方案;而后讲解本文主要研究的 spring-session 的概念和功能;接着演示了 spring-session 的两种管理 sessionid 的实现方案,属于实战内容,需重点掌握;再接着对后台保存数据到 redis 上的数据结构进行了分析;而后对 spring-session 的核心源代码进行了解读,方便理解 spring-session 框架的实现原理;最后列举了在使用 spring-session 的实践过程当中可能遇到的问题或坑,重点去理解一下。html
HttpSession 是经过 Servlet 容器建立和管理的,像 Tomcat/Jetty 都是保存在内存中的。而若是咱们把 web 服务器搭建成分布式的集群,而后利用 LVS 或 Nginx 作负载均衡,那么来自同一用户的 Http 请求将有可能被分发到两个不一样的 web 站点中去。那么问题就来了,如何保证不一样的 web 站点可以共享同一份 session 数据呢?html5
最简单的想法将 session 管理从容器中独立出来。而实现方案有不少种,下面简单介绍下:java
Spring Session 是 Spring 的项目之一,GitHub地址:https://github.com/spring-pro...。 git
Spring Session 提供了一套建立和管理 Servlet HttpSession 的完美方案。github
spring Session 提供了 API 和实现,用于管理用户的 Session 信息。除此以外,它还提供了以下特性:web
<!-- spring-session-data-redis 是一个空的包,仅仅只有一个 META-INF 文件夹。它的做用就在于引入以下四个 包 spring-data-redis,jedis,spring-session,commons-pool2 --> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> <version>1.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.6.1.RELEASE</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.5.2</version> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session</artifactId> <version>1.1.0.RELEASE</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.2</version> </dependency>
<!-- redis 的 bean 配置以下 --> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"/> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <property name="hostName" value="127.0.0.1" /> <property name="port" value="6379" /> <property name="password" value="" /> <property name="timeout" value="3600" /> <property name="poolConfig" ref="jedisPoolConfig" /> <property name="usePool" value="true" /> <property name="database" value="0"/> <!-- 默认存放在0号库中 --> </bean> <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate"> <property name="connectionFactory" ref="jedisConnectionFactory" /> </bean> <!-- 将 session 放入 redis, spring-session 会使用此 bean --> <bean id="redisHttpSessionConfiguration" class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"> <property name="maxInactiveIntervalInSeconds" value="1800" /> </bean>
这里前面几个 bean 都是操做 redis 时候使用的,最后一个 bean 才是 spring-session 须要用到的,其中的 id 能够不写或者保持不变,这也是一个约定优先配置的体现。这个 bean 中又会自动产生多个 bean ,用于相关操做,极大的简化了咱们的配置项。其中有个比较重要的是 springSessionRepositoryFilter ,它将在下面的代理 filter 中被调用到。maxInactiveIntervalInSeconds 表示超时时间,默认是 1800 秒。上述配置能够采用 xml 来定义,官方文档中有采用注解来声明一个配置类。redis
接下来在 web.xml 中添加一个 session 代理 filter ,经过这个 filter 来包装 Servlet 的 getSession() 。须要注意的是这个 filter 须要放在全部 filter 链最前面,从而保证彻底替换掉 tomcat 的 session。这个是约定。。算法
<!-- delegatingFilterProxy --> <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> </filter-mapping>
第一步:编写 Controller 代码spring
@RequestMapping(value = "user", method = RequestMethod.POST) public void setUser(HttpSession session) { User user = new User(); user.setName("lyf"); user.setPassword("123"); session.setAttribute("user", user); } @RequestMapping(value = "user", method = RequestMethod.GET) public String getUser(HttpSession session) { User user = (User) session.getAttribute("user"); String name = user.getName(); return "用户名称:" + name; }
第二步:浏览器中访问 Controller数据库
响应头部以下:Response Headers:
Set-Cookie:SESSION=a2c10601-3204-454e-b545-85e84f587045; Path=/training/; HttpOnly ...
会发现浏览器 Cookie 中的 jsessionid 已经替换为 session**
此时使用 redis-cli 到 redis 库中查询以下:
springsession:0>keys * 1) spring:session:sessions:a2c10601-3204-454e-b545-85e84f587045 2) spring:session:expirations:1502595600000
请求头部以下:Request Headers:
Cookie:SESSION=a2c10601-3204-454e-b545-85e84f587045;
服务器经过 Cookie 中的 session 识别码从 redis 库中找到了须要的 session 对象并返回,浏览器显示以下:
用户名称:lyf
经过如上 spring-session 配置便可将其集成到项目中,以后使用的全部有关 session 的操做,都会由 spring-session 来接管建立和信息存取。官方默认 spring-session 中的 session 信息都保存在 redis 数据库中。
此实现方式弊端:若是浏览器禁用掉了 cookie 或者是非 web 请求时根本没有 cookie 的时候,那么如上经过cookie 管理 sessionid 的实现方式将不可以实现 session 共享。
同3.1
<!-- redis 的 bean 配置以下 --> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"/> <!-- 替代默认使用 cookie ,这里使用的是 httpheader --> <bean id="httpSessonStrategy" class="org.springframework.session.web.http.HeaderHttpSessionStrategy"/> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <property name="hostName" value="127.0.0.1" /> <property name="port" value="6379" /> <property name="password" value="" /> <property name="timeout" value="3600" /> <property name="poolConfig" ref="jedisPoolConfig" /> <property name="usePool" value="true" /> </bean> <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate"> <property name="connectionFactory" ref="jedisConnectionFactory" /> </bean> <!-- 将 session 放入 redis --> <bean id="redisHttpSessionConfiguration" class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"> <property name="maxInactiveIntervalInSeconds" value="1800" /> <property name="httpSessionStrategy" ref="httpSessonStrategy"/> </bean>
同3.3
第一步:编写 Controller代码
@RequestMapping(value = "user", method = RequestMethod.POST) public void setUser(HttpSession session) { User user = new User(); user.setName("lyf"); user.setPassword("123"); session.setAttribute("user", user); } @RequestMapping(value = "user", method = RequestMethod.GET) public String getUser(HttpSession session) { User user = (User) session.getAttribute("user"); String name = user.getName(); return "用户名称:" + name; }
第二步:浏览器中访问 Controller
响应头部以下:Response Headers:
x-auth-token:256064c7-b583-460f-bbd2-1f6dab3fd418 ...
区别 Cookie 的地方在于,这种方式在响应头信息中添加了惟一标识字段 x-auth-token
此时使用 redis-cli 到 redis 库中查询以下:
springsession:0>keys * 1) spring:session:expirations:1502597280000 2) spring:session:sessions:256064c7-b583-460f-bbd2-1f6dab3fd418
get 请求:localhost:8080/training/user
请求头部以下:Response Headers:
x-auth-token:00ee4b6a-0aeb-42b1-a2bd-eae6f370c677
会发现此时在响应头信息中又从新建立了一个 x-auth-token ,由于 spring-seesion 的底层实现是在请求的时候服务端若是没有拿到这个惟一标识,就会从新建立一个新的 x-auth-token,
并保存到 redis 库中。
此时使用 redis-cli 到 redis 库中查询以下:
springsession:0>keys * 1) spring:session:sessions:00ee4b6a-0aeb-42b1-a2bd-eae6f370c677 2) spring:session:expirations:1502597280000 3) spring:session:sessions:256064c7-b583-460f-bbd2-1f6dab3fd418 4) spring:session:expirations:1502597460000
所以要想获取到 session 中的用户信息,须要将服务端返回的 x-auth-token 惟一标识符附加到 Headers上,而后服务器根据这个惟一标识符才能找到对应的用户信息
在此过程的 get 请求的 Headers 中添加以下键值对:
x-auth-token:256064c7-b583-460f-bbd2-1f6dab3fd418
服务器经过 Headers 中的 x-auth-token 从 redis 库中找到了须要的 session 对象并返回,浏览器显示以下:
用户名称:lyf
所以:
Spring-session 能够控制客户端和服务器端之间如何进行 sessionid 的交换,这样更加易于编写 Restful API,由于它能够从 HTTP 头信息中获取 sessionid ,而没必要再依赖于 cookie 。
RedisSession 在建立时设置 3 个变量 creationTime ,maxInactiveInterval ,lastAccessedTime 。maxInactiveInterval 默认值为 1800 ,表示 1800s 以内该 session 没有被再次使用,则代表该 session 已过时。每次 session 被访问都会更新 lastAccessedTime 的值, session 的过时计算公式:当前时间-lastAccessedTime > maxInactiveInterval
.
/** * Creates a new instance ensuring to mark all of the new attributes to be * persisted in the next save operation. **/ RedisSession() { this(new MapSession()); this.delta.put(CREATION_TIME_ATTR, getCreationTime()); this.delta.put(MAX_INACTIVE_ATTR, getMaxInactiveIntervalInSeconds()); this.delta.put(LAST_ACCESSED_ATTR, getLastAccessedTime()); this.isNew = true; this.flushImmediateIfNecessary(); } public MapSession() { this(UUID.randomUUID().toString()); }
flushImmediateIfNecessary 判断 session 是否须要当即写入后端存储。
spring session在 redis 里面保存的数据包括:
spring:session:expireations:[min]
min 表示从 1970 年 1 月 1 日 0 点 0 分通过的分钟数, SET 集合的 member 为 expires:[sessionId] ,表示 members 会在 min 分钟后过时。
spring:session:sessions:expires:[sessionId]
该数据的 TTL 表示 sessionId 过时的剩余时间,即 maxInactiveInterval。
spring:session:sessions:[sessionId]
session 保存的数据,记录了 creationTime,maxInactiveInterval,lastAccessedTime,attribute。前两个数据是用于 session 过时管理的辅助数据结构。
获取 session 流程:
应用经过 getSession(boolean create) 方法来获取 session 数据,参数 create 表示 session 不存在时是否建立新的 session 。 getSession 方法首先从请求的 “.CURRENT_SESSION” 属性来获取 currentSession ,没有 currentSession ,则从 request 取出 sessionId ,而后读取 spring:session:sessions:[sessionId] 的值,同时根据 lastAccessedTime 和 MaxInactiveIntervalInSeconds 来判断这个 session 是否过时。若是 request 中没有 sessionId ,说明该用户是第一次访问,会根据不一样的实现,如 RedisSession ,MongoExpiringSession ,GemFireSession 等来建立一个新的 session 。
另外, 从 request 取 sessionId 依赖具体的 HttpSessionStrategy 的实现,spring session 给了两个默认的实现 CookieHttpSessionStrategy 和 HeaderHttpSessionStrategy ,即从 cookie 和 header 中取出 sessionId 。
具体的代码实如今第 4 章已经演示了。
spring session 的有效期指的是访问有效期,每一次访问都会更新 lastAccessedTime 的值,过时时间为lastAccessedTime + maxInactiveInterval ,也即在有效期内每访问一次,有效期就向后延长 maxInactiveInterval。
对于过时数据,通常有三种删除策略:
1)定时删除,即在设置键的过时时间的同时,建立一个定时器, 当键的过时时间到来时,当即删除。
2)惰性删除,即在访问键的时候,判断键是否过时,过时则删除,不然返回该键值。
3)按期删除,即每隔一段时间,程序就对数据库进行一次检查,删除里面的过时键。至于要删除多少过时键,以及要检查多少个数据库,则由算法决定。
redis 删除过时数据采用的是懒性删除+按期删除
组合策略,也就是数据过时了并不会及时被删除。为了实现 session 过时的及时性,spring session 采用了定时删除的策略,但它并非如上描述在设置键的同时设置定时器,而是采用固定频率(1分钟)轮询删除过时值,这里的删除是惰性删除。
轮询操做并无去扫描全部的 spring:session:sessions:[sessionId] 的过时时间,而是在当前分钟数检查前一分钟应该过时的数据,即 spring:session:expirations:[min] 的 members ,而后 delete 掉 spring:session:expirations:[min] ,惰性删除 spring:session:sessions:expires:[sessionId] 。
还有一点是,查看三个数据结构的TTL时间,spring:session:sessions:[sessionId] 和 spring:session:expirations:[min] 比真正的有效期大 5 分钟,目的是确保当 expire key 数据过时后,监听事件还能获取到 session 保存的原始数据。
@Scheduled(cron = "${spring.session.cleanup.cron.expression:0 * * * * *}") public void cleanupExpiredSessions() { this.expirationPolicy.cleanExpiredSessions(); } public void cleanExpiredSessions() { long now = System.currentTimeMillis(); long prevMin = roundDownMinute(now); // preMin 时间到,将 spring:session:expirations:[min], // set 集合中 members 包括了这一分钟以内须要过时的全部 // expire key 删掉, member 元素为 expires:[sessionId] String expirationKey = getExpirationKey(prevMin); Set<Object> sessionsToExpire = this.redis.boundSetOps(expirationKey).members(); this.redis.delete(expirationKey); for (Object session : sessionsToExpire) { // sessionKey 为 spring:session:sessions:expires:[sessionId] String sessionKey = getSessionKey((String) session); // 利用 redis 的惰性删除策略 touch(sessionKey); } }
spring session 在 redis 中保存了三个 key ,为何? sessions key 记录 session 自己的数据,expires key标记 session 的准确过时时间,expiration key 保证 session 可以被及时删除,spring 监听事件可以被及时处理。
上面的代码展现了 session expires key 如何被删除,那 session 每次都是怎样更新过时时间的呢? 每一次 http 请求,在通过全部的 filter 处理事后,spring session 都会经过 onExpirationUpdated() 方法来更新 session 的过时时间, 具体的操做看下面源码的注释。
public void onExpirationUpdated(Long originalExpirationTimeInMilli, ExpiringSession session) { String keyToExpire = "expires:" + session.getId(); long toExpire = roundUpToNextMinute(expiresInMillis(session)); if (originalExpirationTimeInMilli != null) { long originalRoundedUp = roundUpToNextMinute(originalExpirationTimeInMilli); // 更新 expirations:[min] ,两个分钟数以内都有这个 session ,将前一个 set 中的成员删除 if (toExpire != originalRoundedUp) { String expireKey = getExpirationKey(originalRoundedUp); this.redis.boundSetOps(expireKey).remove(keyToExpire); } } long sessionExpireInSeconds = session.getMaxInactiveIntervalInSeconds(); String sessionKey = getSessionKey(keyToExpire); if (sessionExpireInSeconds < 0) { this.redis.boundValueOps(sessionKey).append(""); this.redis.boundValueOps(sessionKey).persist(); this.redis.boundHashOps(getSessionKey(session.getId())).persist(); return; } String expireKey = getExpirationKey(toExpire); BoundSetOperations<Object, Object> expireOperations = this.redis .boundSetOps(expireKey); expireOperations.add(keyToExpire); long fiveMinutesAfterExpires = sessionExpireInSeconds + TimeUnit.MINUTES.toSeconds(5); // expirations:[min] key 的过时时间加 5 分钟 expireOperations.expire(fiveMinutesAfterExpires, TimeUnit.SECONDS); if (sessionExpireInSeconds == 0) { this.redis.delete(sessionKey); } else { // expires:[sessionId] 值为“”,过时时间为 MaxInactiveIntervalInSeconds this.redis.boundValueOps(sessionKey).append(""); this.redis.boundValueOps(sessionKey).expire(sessionExpireInSeconds, TimeUnit.SECONDS); } // sessions:[sessionId] 的过时时间加 5 分钟 this.redis.boundHashOps(getSessionKey(session.getId())) .expire(fiveMinutesAfterExpires, TimeUnit.SECONDS); }
使用 spring-session 须要解决两个核心问题:
问题一:如何建立集群环境下高可用的 session,要求可以可靠并高效地存储数据
解决:在高可用可扩展的集群中存储数据已经经过各类数据存储方案获得了解决,如 Redis、GemFire 以及 Apache Geode 等等
问题二:如何保证无论请求是 HTTP、WebSocket 等其余协议,服务端都可以获取到 sessionid 来找到对应的资源
解决:Spring Session 认为将请求与特定的 session 实例关联起来的问题是与协议相关的,由于在请求/响应周期中,客户端和服务器之间须要协商赞成一种传递 sessionid 的方式。例如,若是请求是经过 HTTP 传递进来的,那么 session 能够经过 HTTP cookie 或 HTTP Header 信息与请求进行关联。若是使用 HTTPS 的话,那么能够借助SSL sessionid 实现请求与 session 的关联。若是使用 JMS 的话,那么 JMS 的 Header 信息可以用来存储请求和响应之间的 sessionid 。
Spring Session 对 HTTP 的支持是经过标准的 servlet filter 来实现的,这个 filter 必需要配置为拦截全部的 web 应用请求,而且它应该是 filter 链中的第一个 filter 。Spring Session filter 会确保随后调用javax.servlet.http.HttpServletRequest
的getSession()
方法时,都会返回 Spring Session 的HttpSession
实例,而不是应用服务器默认的 HttpSession。
首先,咱们了解一下标准 servlet 扩展点的一些背景知识:
在2001年,Servlet 2.3规范引入了ServletRequestWrapper
。官方API中解释,ServletRequestWrapper
“提供了ServletRequest
接口的便利实现,开发人员若是但愿将请求适配到 Servlet 的话,能够编写它的子类。这个类实现了包装(Wrapper)或者说是装饰(Decorator)模式。对方法的调用默认会经过包装的请求对象来执行”。以下的代码样例抽取自 Tomcat,展示了 ServletRequestWrapper 是如何实现的。
public class ServletRequestWrapper implements ServletRequest { private ServletRequest request; /** * 建立 ServletRequest 适配器,它包装了给定的请求对象。 */ public ServletRequestWrapper(ServletRequest request) { if (request == null) { throw new IllegalArgumentException("Request cannot be null"); } this.request = request; } public ServletRequest getRequest() { return this.request; } public Object getAttribute(String name) { return this.request.getAttribute(name); } }
Servlet 2.3 规范还定义了HttpServletRequestWrapper
,它是ServletRequestWrapper
的子类,可以快速提供HttpServletRequest
的自定义实现,以下的代码是从 Tomcat 抽取出来的,展示了HttpServletRequesWrapper
类是如何运行的。
public class HttpServletRequestWrapper extends ServletRequestWrapper implements HttpServletRequest { public HttpServletRequestWrapper(HttpServletRequest request) { super(request); } private HttpServletRequest _getHttpServletRequest() { return (HttpServletRequest) super.getRequest(); } public HttpSession getSession(boolean create) { return this._getHttpServletRequest().getSession(create); } public HttpSession getSession() { return this._getHttpServletRequest().getSession(); } }
因此,借助这些包装类就能编写代码来扩展HttpServletRequest
,重载返回HttpSession
的方法,让它返回由外部存储所提供的实现。以下的代码是从 Spring Session 项目中提取出来的。
/* * 注意,Spring Session 项目定义了扩展自 * 标准 HttpServletRequestWrapper 的类,用来重载 * HttpServletRequestWrapper 中与 session 相关的方法。 */ private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper { private HttpSessionWrapper currentSession; private Boolean requestedSessionIdValid; private boolean requestedSessionInvalidated; private final HttpServletResponse response; private final ServletContext servletContext; /* * 注意,这个构造器很是简单,它接收稍后会用到的参数, * 而且委托给它所扩展的 HttpServletRequestWrapper */ private SessionRepositoryRequestWrapper( HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) { super(request); this.response = response; this.servletContext = servletContext; } /* * 在这里,Spring Session 项目再也不将调用委托给 * 应用服务器,而是实现本身的逻辑, * 返回由外部数据存储做为支撑的 HttpSession 实例。 * * @Param create 参数表示 session 不存在时是否建立新的 session */ @Override public HttpSession getSession(boolean create) { // 检查是否存在 session ,若是存在,则直接返回 if(currentSession != null) { return currentSession; } // 检查当前的请求中是否存在 sessionid String requestedSessionId = getRequestedSessionId(); if(requestedSessionId != null) { // 若是存在 sessionid ,将会根据这个 sessionid,从它的 SessionRepository 中加载 session S session = sessionRepository.getSession(requestedSessionId); if(session != null) { // 封装 session 并返回 this.requestedSessionIdValid = true; currentSession = new HttpSessionWrapper(session, getServletContext()); currentSession.setNew(false); return currentSession; } } if(!create) { return null; } // session repository 中没有 session ,而且在当前请求中也没有与之关联的 sessoinid, // 那么就建立一个新的 session ,并将其持久化到 session repository 中 S session = sessionRepository.createSession(); currentSession = new HttpSessionWrapper(session, getServletContext()); return currentSession; } @Override public HttpSession getSession() { return getSession(true); } }
Spring Session 定义了SessionRepositoryFilter
,它实现了 Servlet Filter
接口。以下是抽取了这个 filter的关键部分
/* * SessionRepositoryFilter 只是一个标准的 ServletFilter, * 它的实现扩展了一个 helper 基类。 */ public class SessionRepositoryFilter < S extends ExpiringSession > extends OncePerRequestFilter { /* * 这个方法是魔力真正发挥做用的地方。这个方法至关于重写了doFilter, * 建立了咱们上文所述的封装请求对象和 * 一个封装的响应对象,而后调用其他的 filter 链。 * 这里,关键在于当这个 filter 后面的应用代码执行时, * 若是要得到 session 的话,获得的将会是 Spring Session 的 * HttpServletSession 实例,它是由后端的外部数据存储做为支撑的。 */ protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { request.setAttribute(SESSION_REPOSITORY_ATTR, sessionRepository); SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request,response,servletContext); SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest, response); HttpServletRequest strategyRequest = httpSessionStrategy.wrapRequest(wrappedRequest, wrappedResponse); HttpServletResponse strategyResponse = httpSessionStrategy.wrapResponse(wrappedRequest, wrappedResponse); try { filterChain.doFilter(strategyRequest, strategyResponse); } finally { wrappedRequest.commitSession(); } } }
总结:经过对 spring-session 核心源码的分析获得的关键信息是,Spring Session 对 HTTP 的支持所依靠的是一个简单老式的ServletFilter
,借助 servlet 规范中标准的特性来实现 Spring Session 的功能。所以,咱们可以让已有的 war 文件使用 Spring Session 的功能,而无需修改已有的代码。
默认状况下,session 存储在 redis 的 key 是“spring:session::”,但若是有多个系统同时使用一个 redis,则会冲突,此时应该配置 redisNamespace 值,配置后,其 key 为 spring:session:devlops:keyName
配置 redisNamesapce 的方式,在以前配置文件的 bean 中添加一个属性便可
<!-- 将session放入redis --> <bean id="redisHttpSessionConfiguration" class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"> <property name="maxInactiveIntervalInSeconds" value="1800" /> <property name="redisNamespace" value="${redisNamespace}"/> </bean>
注意:spring-session 的版本在 1.1.0 及以上才支持命名空间
Serializable
接口,这样 Spring-session 才能对保存的对象进行序列化,从而存储在 redis 里若是选用 redis 云服务,使用过程当中会出现异常,异常缘由是:不少 Redis 云服务提供商考虑到安全因素,会禁用掉 Redis 的 config 命令,所以须要咱们手动在云服务后台管理系统手动配置,或者找云服务售后帮忙配置。而后咱们在配置文件 RedisHttpSessionConfiguration 的 bean 中添加以下配置,解决使用 redis 云服务异常问题
<!-- 让Spring Session再也不执行config命令 --> <util:constant static-field="org.springframework.session.data.redis.config.ConfigureRedisAction.NO_OP"> </util:constant>
注意:判断 config 命令是否被禁用,能够在 redis 的命令行去使用 config 命令,若是报没有找到该命令,说明 config 命令被禁用了。