一.使用场景

应用部署在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);
}

 

12-03 09:36