利用缓存实现session共享

Posted 十一路客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了利用缓存实现session共享相关的知识,希望对你有一定的参考价值。

一.使用场景

应用部署在A,B两台服务器上时,此时若一用户在A服务器上登录后,登录信息会存放在A服务器上的session中,之后若该用户的请求被分配到B服务器上,会出现请求错误,因为B服务器上没有该用户的登录信息,因此考虑将session放在缓存中,实现session在多个服务器间的共享。(其他方案:将session存放在cookie[不安全],或者数据库[速度慢]中)

二. 解决方案

将session存到缓存中,封装HttpSessionWrapper类,对session的使用还和之前一样

1.HttpServletRequestWrapper类

//处理session共享  将session存到mdb中 封装HttpServletRequestWrapper
public class HttpServletRequestWrapper extends javax.servlet.http.HttpServletRequestWrapper
    //sessionId
    private String sid;
    //session实例
    private SessionService sessionService;

    public HttpServletRequestWrapper(String sid,HttpServletRequest request
            , SessionService sessionService)
        super(request);
        this.sid = sid;
        this.sessionService = sessionService;
    

    @Override
    public HttpSession getSession(boolean create)
        return new HttpSessionWrapper(this.sid,super.getSession(create),this.sessionService);
    

    @Override
    public HttpSession getSession() 

        return new HttpSessionWrapper(this.sid,super.getSession(),this.sessionService);
    

2.HttpSessionWrapper类

public class HttpSessionWrapper implements HttpSession

    private final Logger logger = LoggerFactory.getLogger(HttpSessionWrapper.class);
    //session过期时间 单位是秒
    private static final Integer EXPIRE_TIME = 3600;

    //sessionID 自定义sessionId 否则会出现两个服务器生成的sessionid不一样的情况
    private String sid;
    //HashMap 对应session里的属性值
    private Map map = new HashMap();

    private SessionService sessionService;
    private HttpSession session;

    public HttpSessionWrapper(String sid, HttpSession session, SessionService sessionService)
        this.sid = sid;
        this.sessionService = sessionService;
        this.session = session;

        //根据session对map进行初始化
        Map memSession = null;
        String sessionJsonStr = this.sessionService.getSessionBySID(this.sid);

        if(sessionJsonStr == null || sessionJsonStr.equals("") ||sessionJsonStr.equals(""))
            memSession = null;
        else 
            memSession = JSON.parseObject(sessionJsonStr, Map.class);
        

        //sid没有加入到session中,需要初始化session,替换为自定义session
        if(memSession == null)
            memSession = new HashMap();
            if(session != null)
                Enumeration<String> names = session.getAttributeNames();
                while(names.hasMoreElements())
                    String key = names.nextElement();
                    memSession.put(key,session.getAttribute(key));
                
            
        //if

        this.map = memSession;

        //logger.info("initial HttpSessionWrapper");
        //不能在这里更新  会出错 session内容会被置为null
        //attributeChange();

    

    //tair value需要是可序列化的 因此这里将map转化为了json 每访问一次session 需更新session有效期
    private void attributeChange() 
        String sessionId = this.sid;
        String content = JSON.toJSONString(this.map);

        HashMap<String, Object> param = new HashMap<String, Object>();
        param.put("sessionId", sessionId);
        param.put("content", content);
        param.put("expireTime", HttpSessionWrapper.EXPIRE_TIME);

        Integer count = this.sessionService.updateSession(param);
        if(count <= 0)
            logger.error("attributeChange put sid: " + sessionId + " failure");
        
    

    @Override
    public Object getAttribute(String key)
        logger.info("HttpSessionWrapper getAttribute name: " + key);
        //这里不能加更新语句  因为构造函数循环中调用getAttribute时 this.map还没赋值完
        //attributeChange();
        if(this.map != null && this.map.containsKey(key))
            Object value = this.map.get(key);
            logger.info("value: " + value);
            if(value != null)
                logger.info(value.getClass().toString()); //class com.alibaba.fastjson.JSONObject
            
            if(key.equals(Constants.SESSION_LOGIN_USER))//登录用户
                TUser userObj = JSON.parseObject(value.toString(),TUser.class);
                return userObj;
            else if(key.equals(Constants.SESSION_MENU_LIST))//菜单列表
                List<TMenu> menuList = JSON.parseArray(value.toString(),TMenu.class);
                return menuList;
            else if(key.equals("javax.security.auth.subject"))
                //20180327 add
                Subject object = JSON.parseObject(value.toString(), Subject.class);
                logger.info(object.getClass().toString());
                return object;
            else
                return value;
            
        else
            return null;
        
    

    @Override
    public Enumeration<String> getAttributeNames()
        logger.info("HttpSessionWrapper getAttributeNames");
        Set temp = this.map.keySet();
        //attributeChange();
        return new Vector(temp).elements();
    

    @Override
    public void removeAttribute(String name)
        logger.info("HttpSessionWrapper removeAttribute name: " + name);
        this.map.remove(name);
        attributeChange();
    

    @Override
    public void setAttribute(String name, Object value)
        logger.info("HttpSessionWrapper setAttribute name: " + name);
        this.map.put(name,value);
        attributeChange();
    


    @Override
    public void invalidate()
        logger.info("HttpSessionWrapper invalidate");
        this.map.clear();
        long s1= System.currentTimeMillis();
        try 
            Integer count = this.sessionService.deleteSessionBySID(this.sid);
            logger.info("removeSession sid is:" + this.sid + "; count: " + count);
        
        finally
            logger.info("used time: " + (System.currentTimeMillis() - s1));
        

    

    //以下没有用缓存mdb实现
    @Override
    public long getCreationTime() 
        return this.session.getCreationTime();
    

    @Override
    public String getId() 
        return this.sid;
    

    @Override
    public long getLastAccessedTime() 
        return this.session.getLastAccessedTime();
    

    @Override
    public ServletContext getServletContext() 
        return this.session.getServletContext();
    

    @Override
    public void setMaxInactiveInterval(int i) 
       this.session.setMaxInactiveInterval(i);
    

    @Override
    public int getMaxInactiveInterval() 
        return this.session.getMaxInactiveInterval();
    

    @Override
    public HttpSessionContext getSessionContext() 
        return this.session.getSessionContext();
    

    @Override
    public boolean isNew() 
        return this.session.isNew();
    
    @Override
    public Object getValue(String s) 
        return this.session.getValue(s);
    

    @Override
    public String[] getValueNames() 
        return this.session.getValueNames();
    
    @Override
    public void removeValue(String s) 
        this.session.removeValue(s);
    
    @Override
    public void putValue(String s, Object o) 
        this.session.putValue(s,o);
    


3.filter配置

@Component
public class ApplicationFilterConfig 
    @Bean
    public FilterRegistrationBean filterRegistrationBean()
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        MdbSessionFilter sessionFilter = new MdbSessionFilter();
        registrationBean.setFilter(sessionFilter);
        List<String> urlPatterns = new ArrayList<String>();
        urlPatterns.add("/*");
        registrationBean.setUrlPatterns(urlPatterns);

        return registrationBean;
    

4.filter定义

//为实现session共享 创建Filter 重新封装request
public class MdbSessionFilter implements Filter 
    private static final Logger logger = LoggerFactory.getLogger(MdbSessionFilter.class);

    //这里不可使用autowired注解注入
    private SessionService sessionService;

    private static final Set<String> noValidRoutes = new HashSet();
    static 
        //必须加/
//        noValidRoutes.add("/");
//        noValidRoutes.add("/index");
//        noValidRoutes.add("/login");
//        noValidRoutes.add("/verifyCode");
        noValidRoutes.add("/checkpreload.htm"); 
        
    

    @Override
    public void init(FilterConfig filterConfig) throws ServletException 
        logger.info("init MdbSessionFilter");
        ServletContext context = filterConfig.getServletContext();
        ApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(context);
        sessionService = (SessionService) ac.getBean("sessionService");

    

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException 
        String sessionId = Constants.SESSION_COOKIE_NAME;
        //logger.info("sessionId: " + sessionId);

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        String uri = request.getRequestURI();

        if(noValidRoutes.contains(uri))
            filterChain.doFilter(request,response);
            return;
        else
            logger.info("MdbSessionFilter uri: " + uri);
        

        Cookie cookies[] = request.getCookies();
        Cookie sCookie = null;
        String sid = "";

        //sessionId已经存放在cookie中 直接取出 放入sid中
        if (cookies != null && cookies.length > 0) 
            for (int i = 0; i < cookies.length; i++) 
                sCookie = cookies[i];
                if (sCookie.getName().equals(sessionId)) 
                    sid = sCookie.getValue();
                
            
        

        if (sid == null || sid.length() == 0) 
            //生成sessionID
            sid = java.util.UUID.randomUUID().toString();
            logger.info("sid: " + sid);
            Cookie mycookie = new Cookie(sessionId, sid);
            //设置生命周期为1天,秒为单位
            mycookie.setMaxAge(-1);
            mycookie.setPath("/");
            mycookie.setHttpOnly(true);
            response.addCookie(mycookie);
        

        //logger.info("sessionId: " + sessionId + " -- sid: " + sid);

        filterChain.doFilter(new HttpServletRequestWrapper(sid, request, sessionService), response);

    

    @Override
    public void destroy() 

    

5.SessionService

public interface SessionService 
    /**
     * 根据sid获取session内容
     * @param sid sessionId
     * @return session内容 json字符串
     * */
    String getSessionBySID(String sid);

    /**
     * 根据sessionId更新session
     * @param param -- sessionId content expireTime
     * @return 影响行数
     * */
    Integer updateSession(HashMap<String, Object> param);

    /**
     * 根据sessionId删除session
     * @param sid sessionId
     * @return 影响行数
     * */
    Integer deleteSessionBySID(String sid);

 

以上是关于利用缓存实现session共享的主要内容,如果未能解决你的问题,请参考以下文章

Redis 分布式缓存,是如何实现多台服务器SESSION 实时共享的

tomcat+redis实现session共享缓存

redis缓存和cookie实现Session共享

Spring Boot 多站点利用 Redis 实现 Session 共享

利用Redis 实现共享Session

Shiro如何使用Ehcache实现Session共享