自定义实现session持久化

使用场景

对于有登录校验的网站,tomcat 重启之后,刷新页面又得重新登录,影响用户体验.

原因:

tomcat 的session 在内存中,tomcat重启之后,内存中的session就销毁了.导致登录信息丢失

session持久化的目的

对于存储在session中的信息,服务器重启之后,不会丢失. 比如用户登录之后,重启tomcat服务器,刷新页面,依然是登录状态.

目标

重新实现session的常用操作,如 getAttribute(String s) , setAttribute(String s, Object o) , removeAttribute(String s) ;

编写业务代码时无侵入,也就是说,实际编写业务代码时不用使用专门的API,仍然像之前一样操作HttpSession.

思路

增加过滤器 javax.servlet.Filter的实现类;

在请求之前包装 HttpServletRequest,例如包装类是SessionSyncRequestWrapper;

业务代码中调用 getSession 时,就会调用包装类SessionSyncRequestWrapper 的 getSession, 我们只要在包装类SessionSyncRequestWrapper 中,重写getSession 即可.

自定义HttpSession 的包装类 CustomSharedHttpSession,继承HttpSession,

在CustomSharedHttpSession 中重写HttpSession的方法

实现方案

1. 自定义过滤器RequestbodyFilter,实现javax.servlet.Filter;

在RequestbodyFilter 中使用责任链设计模式编写一套自定义的请求过滤器

主要类如下:

链条:

public class RequestFilterChain {

private List filterList = new ArrayList<>();

private int index = 0;

private boolean hasAddDefaultFilter = false;

public RequestFilterChain addFilter(IRequestFilter filter) {

if (hasAddDefaultFilter) {

throw new RuntimeException("自定义过滤器必须在默认过滤器之前添加");

}

this.filterList.add(filter);

return this;

}

public RequestFilterChain addDefaultFilter(IRequestFilter filter) {

this.filterList.add(filter);

hasAddDefaultFilter = true;

// DefaultFormRequestWrapperFilter defaultDaoFilter = (DefaultFormRequestWrapperFilter) filter;

return this;

}

public void reset() {

this.index = 0;

}

private IRequestFilter next() {

if (index == filterList.size()) {

return null;

}

IRequestFilter filter = filterList.get(index);

index++;

return filter;

}

public void doFilter(HttpPutFormContentRequestWrapper request, HttpServletResponse response) throws IOException, ServletException {

IRequestFilter filter = next();

if (null == filter) {

System.out.println("结束 index :" + index);

return;

}

filter.doFilter(request, response, this);

}

}

请求处理接口 :

public interface IRequestFilter {

void doFilter(HttpPutFormContentRequestWrapper request, HttpServletResponse response, RequestFilterChain filterChain) throws IOException, ServletException;

}

在RequestbodyFilter 中处理链条:

wrapper.setChain(chain);

RequestFilterChain requestFilterChain = new RequestFilterChain();

addRequestFilter(requestFilterChain);

HttpServletRequest httpServletRequest1 = (HttpServletRequest) request;

boolean isLocalIp = WebServletUtil.isLocalIp(httpServletRequest1);

if (!isLocalIp) {

requestFilterChain.addFilter(new DecideUseCacheWhenOvertimeFilter());

}

requestFilterChain.addDefaultFilter(new DefaultFormRequestWrapperFilter());

requestFilterChain.doFilter(wrapper, (HttpServletResponse) response);

wrapper.resetCustom();

2. HttpSessionSyncShareFilter实现自定义请求接口 IRequestFilter

HttpSessionSyncShareFilter 中做了两件事:

获取session持久化方案,目前支持 database 和redis;

对请求request进行包装,包装成为 SessionSyncRequestWrapper;

HttpSessionSyncShareFilter中核心方法:

@Override

public void doFilter(HttpPutFormContentRequestWrapper request, HttpServletResponse response, RequestFilterChain filterChain) throws IOException, ServletException {

ISharableSessionAPI sharedSessionAPI = getSharableSessionAPI(request, this.sessionImplType);

System.out.println("sharableSessionAPI :" + sharedSessionAPI.getClass().getSimpleName());

SessionSyncRequestWrapper sessionSyncRequestWrapper = new SessionSyncRequestWrapper(request, sharedSessionAPI);

if (null == sessionSyncRequestWrapper.getChain()) {//NOTICE:一定要有这个判断

sessionSyncRequestWrapper.setChain(request.getChain());

}

filterChain.doFilter(sessionSyncRequestWrapper, response);

}

3. 请求包装类SessionSyncRequestWrapper

在SessionSyncRequestWrapper 中重写 getSession(boolean create) ,这样在业务代码中调用getSession(boolean create)方法时,

获取的是我们自定义的session处理类 CustomSharedHttpSession,

CustomSharedHttpSession 继承 javax.servlet.http.HttpSession

4. CustomSharedHttpSession 重写HttpSession的三个常用方法

getAttribute

/***

* 需要重写

* @param s

* @return

*/

@Override

public Object getAttribute(String s) {

Object o1 = null;

if (null == this.getHttpSession()) {

o1 = sharedSessionAPI.getSessionAttribute(this.JSESSIONID, s);

return o1;

}

Object o = this.httpSession.getAttribute(s);

if (o == null) {

String currentSessionId = this.httpSession.getId();

o = sharedSessionAPI.getSessionAttribute(currentSessionId, s);

if (null != o) {

//使用新的 JSESSIONID 保存到redis 中

this.setAttribute(s, o);

return o;

}

if ((!currentSessionId.equals(this.JSESSIONID))) {

Object o2 = sharedSessionAPI.getSessionAttribute(this.JSESSIONID, s); //RedisCacheUtil.getSessionAttribute(this.JSESSIONID + s);

if (null != o2) {

this.httpSession.setAttribute(s, o2);

//此时 this.JSESSIONID有值,但是currentSessionId没有值,所有要手动同步

sharedSessionAPI.setSessionAttribute(currentSessionId, s, o2);

o = o2;

}

}

}

return o;

}

setAttribute

/**

* 需要重写

*

* @param s

* @param o

*/

@Override

public void setAttribute(String s, Object o) {

String sessionId = null;

if (null == this.httpSession) {

sessionId = this.JSESSIONID;

} else {

this.httpSession.setAttribute(s, o);

sessionId = this.httpSession.getId();

}

sharedSessionAPI.setSessionAttribute(sessionId, s, o);

}

removeAttribute

@Override

public void removeAttribute(String s) {

if (null != this.httpSession) {

this.httpSession.removeAttribute(s);

String sessionId = this.httpSession.getId();

sharedSessionAPI.setSessionAttribute(sessionId, s, null);

}

sharedSessionAPI.setSessionAttribute(this.JSESSIONID, s, null);

}

难点解析

CustomSharedHttpSession的构造方法有三个参数:

原始的javax.servlet.http.HttpSession

JSESSIONID :从请求头中获取的JSESSIONID;

ISharableSessionAPI:接口,自定义session属性和值的操作.

ISharableSessionAPI 接口 :

/***

* see CustomSharedSessionAPI,CustomSharedHttpSession

*/

public interface ISharableSessionAPI {

Object getSessionAttribute(String sessionId, String key);

void setSessionAttribute(String sessionId, String s, Object o);

}

getAttribute(String s) 中的逻辑有点复杂,我们进行详细解析

在(1)中,尝试获取原始的session,

如果原始的session为空,则调用ISharableSessionAPI获取属性值,直接返回,

并没有判断属性值是否为空.

在(2)中,如果原始session不为空,则从原始session获取属性值o,

如果o不为空则直接返回,

因为已经取到值了,没有必要从ISharableSessionAPI 中获取.

在(3)中,如果o为空,就需要尝试从ISharableSessionAPI 中获取了;

先拿原始session的id 作为key,来获取,

如果属性值不为空,则同步到原始session中,因为刚才在(2)中得知原始session没有属性值.

然后返回.

进入步骤(4)中,说明 以原始session的id 作为key没有获取到值,

那么以this.JSESSIONID 作为key,调用ISharableSessionAPI 获取属性值,

如果获取到值,则同步到原始session,

(5)中为什么又要设置一遍ISharableSessionAPI的保存呢?

这里是关键!!!

这里是关键!!!

这里是关键!!!

原因如下:

我们按照时间顺序走一遍流程:

浏览器第一次访问服务器, 服务器生成原始session,比如key为AAA,

登录时,保存username到原始session 和ISharableSessionAPI 中.

此时tomcat重启了,

第二次访问,浏览器带过去cookie ,sessionid :AAA,

但是tomcat重启之后,原来的session属性都没了,所以通过AAA获取不到属性值,

tomcat会生成新的session id : BBB 于是以AAA为key,调用ISharableSessionAPI,成功获取到值,并且同步到session id : BBB,

如果没有步骤(5)的话, (5)的作用是把属性值同步到key为AAA的ISharableSessionAPI中.

此时tomcat又重启了,

第二次访问,浏览器带过去cookie ,sessionid :BBB,

tomcat重启之后,原来的session属性都没了,所以通过BBB获取不到属性值,

tomcat会生成新的session id : CCC

于是以BBB为key,调用ISharableSessionAPI,不可能获取到值,

这就出现bug了,本来是有属性值的,重启一次,可以获取,重启第二次,就无法获取了.

总结

我们自定义的session持久化机制,是根据浏览器 cookie中 JSESSIONID 来关联登录信息的,

但是tomcat每重启次,session id会变化,所以才需要步骤(5)

我们只需要实现ISharableSessionAPI,就可以完成session持久化的功能

java session 持久化_自定义实现session持久化相关推荐

  1. java session机制_如何学习Session的机制使用

    session的引入: 鉴于http是无状态的协议,当完成客户端和服务器的传递信息后,就断开之间的联系,这样就造成了怎么样在几个页面之间传递信息 比如客户端信息,或者假如是写一个购物程序,就要记得购物 ...

  2. python消息队列框架持久化_消息队列如果持久化到数据库的话,相对于直接操作数据库有啥优势?...

    MQ的作用很多,典型作用: 1.削峰填谷:如果短时间内要处理的业务量大于数据库的服务能力,则可能会卡死数据库:使用MQ可以慢慢处理. 2.异步化:如果处理的工作非常耗时,则RPC的请求一直halt,对 ...

  3. java validate校验_自定义工具类实现validate参数校验

    前言 相信项目中做一些htttp接口,避免不了要对参数进行校验,大多数情况下,其实我们只是校验是否为NULL就可以了 1.通过注解实现各种状态的字段 1.1.引入依赖 默认的版本是6.0.9.Fina ...

  4. java 日志切面_自定义注解+面向切面整合的日志记录模块(一)

    java中的常见注解 jdk的自带注解 @Override:告诉编译器我重写了接口方法 @Deprecated:告诉编译器这个方法过时了,不建议使用,Ide会在方法上划横线 @SuppressWarn ...

  5. java授权失败_自定义Spring Security的身份验证失败处理方法

    1.概述 在本快速教程中,我们将演示如何在Spring Boot应用程序中自定义Spring Security的身份验证失败处理.目标是使用表单登录方法对用户进行身份验证. 2.认证和授权(Authe ...

  6. java 地图报表_自定义地图功能介绍

    自定义动态地图利用动态的和互动式的falsh地图帮助您按类别,地区或实体显示地理数据.用户可以用它有效地按以下因素绘制业务数据,如地区产品销售额,地区税收,户口普查资料,如国家人口数,选举结果和统计调 ...

  7. java sessionstate_在Java Web开发中自定义Session

    Session在存储安全性要求较高的会话信息方面是必不可少的,对于分布式Web应用自定义Session支持独立的状态服务器或集群是必须的.本文就来教大家如何在Java Web开发中自定义Session ...

  8. java 重写session_使用Shiro重写Session 自定义SESSION

    引入shiro的jar包 1.2.3 org.apache.shiro shiro-core ${shiroVersion} org.apache.shiro shiro-web ${shiroVer ...

  9. java session验证码_利用session实现一次性验证码

    带有验证码的登录页面 用户名: 密码: 验证码: import java.io.*; import javax.servlet.*; import javax.servlet.http.*; impo ...

最新文章

  1. linux 中的source命令
  2. DRF_APIView之认证、授权配置
  3. php下载的文件不是汉字,php实现支持中文的文件下载功能示例
  4. Linux查看系统版本命令 linux学习教程
  5. linux ss命令查看端口监听情况
  6. 对称密钥加密和非对称密钥加密
  7. 1、spring源码解析之概况流程
  8. java怎么打印反三角形_Java基础练习——打印正反三角形
  9. HTML5游戏引擎(十七)-egret引擎实战——踩格子游戏
  10. Coil:为Kotlin而生的图片加载框架
  11. 将二维数组中最大值的 行和列的下标打印出来
  12. php snappy,php – 使用Knp Snappy生成pdf文件时出现错误字符
  13. Firefox for Mac(火狐浏览器 mac)一款速度快到飞起的浏览器
  14. C语言练习题——函数
  15. 【已解决】error: conflicting declaration ‘typedef struct LZ4_stream_t LZ4_stream_t’ typedef struct { long
  16. 树莓派新手使用iobroker日志三(米家全家桶加入iobroker)
  17. Tian Ji -- The Horse Racing(田忌赛马)/贪心算法
  18. 软件项目管理第4版课后习题[附解析]第八章
  19. UVa 109 - SCUD Busters(凸包计算)
  20. 百度webupload的使用

热门文章

  1. 基于CSS3实现垂直轮播效果
  2. C语言图像处理二值图细化,Visual C 实现二值图像处理
  3. 7-46 最长对称子串 (25 分)
  4. 7-1 一元多项式求导 (10 分)
  5. spring boot配置虚拟路径(替代docBase配置)访问本地图片
  6. oracle批量替换保留字,oracle保留字大全
  7. html怎么设置字体竖直,CSS3 writing-mode 控制字体竖直显示
  8. python变量和对象,切片列表元祖
  9. call() apply() bind()
  10. day-01(html)