博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
自定义实现session持久化
阅读量:7257 次
发布时间:2019-06-29

本文共 7779 字,大约阅读时间需要 25 分钟。

hot3.png

自定义实现session持久化

使用场景

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

原因:

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

session持久化的目的

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

目标

  1. 重新实现session的常用操作,如 getAttribute(String s) , setAttribute(String s, Object o) , removeAttribute(String s) ;
  2. 编写业务代码时无侵入,也就是说,实际编写业务代码时不用使用专门的API,仍然像之前一样操作HttpSession.

思路

  1. 增加过滤器 javax.servlet.Filter的实现类;
  2. 在请求之前包装 HttpServletRequest,例如包装类是SessionSyncRequestWrapper;
  3. 业务代码中调用 getSession 时,就会调用包装类SessionSyncRequestWrapper 的 getSession, 我们只要在包装类SessionSyncRequestWrapper 中,重写getSession 即可.
  4. 自定义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 中做了两件事:

  1. 获取session持久化方案,目前支持 database 和redis;
  2. 对请求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的三个常用方法

  1. 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;    }
  1. 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);    }
  1. 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的构造方法有三个参数:

  1. 原始的javax.servlet.http.HttpSession
  2. JSESSIONID :从请求头中获取的JSESSIONID;
  3. 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了,本来是有属性值的,重启一次,可以获取,重启第二次,就无法获取了.

总结

  1. 我们自定义的session持久化机制,是根据浏览器 cookie中 JSESSIONID 来关联登录信息的,
    但是tomcat每重启次,session id会变化,所以才需要步骤(5)
  2. 我们只需要实现ISharableSessionAPI,就可以完成session持久化的功能

代码:

转载于:https://my.oschina.net/huangweiindex/blog/2245362

你可能感兴趣的文章
简单的纯js三级联动
查看>>
linq 获取列表最大值
查看>>
maven命令解释
查看>>
Python easyGUI 登录框 非空验证
查看>>
阿里工程师下乡与一个瓜农的“北伐”
查看>>
同样是索尼IMX380 但夜间成像比华为P20更毒
查看>>
大麦携演协发布演出市场报告:95后成消费新贵 城市下沉提速
查看>>
OPPO R11s智选双摄实力不凡:带来手机暗光拍摄技术新突破
查看>>
阿里云与Intel开启“TOP游戏”云生态培育计划,共建精品游戏生态
查看>>
世界那么大,我们一起到处去看看
查看>>
从大起到大落 各国的虚拟货币市场有何转变?
查看>>
新西兰天维网:新西兰净移民数量呈下降趋势
查看>>
婴儿患小儿脐疝肚子鼓起 父亲竟一刀划开肚脐“放气”
查看>>
英首相提交“脱欧”替代方案 重申不寻求二次公投
查看>>
不放弃!西班牙两岁男童落井8天 救援队仍钻井营救
查看>>
兰州火车站扩能改造完成 正式投入使用
查看>>
宁夏首票关税保证保险报关单顺利通关
查看>>
贷款增速达12.6% 银行业服务实体经济能力提升
查看>>
南方持续强降雪 京广高铁部分列车晚点1到3小时
查看>>
阿里程序员吐槽:玩命赚钱依旧抵不过拆迁户,奋斗的意义呢
查看>>