项目开发用到shiro来管理用户的权限,用memcache的超时机制来管理shiro的session.但是发现在运行项目的时候,访问一个页面控制台会打出很多读取和更新session的日志内容。在通过测试之后发现一次访问shiro自身会去读取和更新多次session。这样如果用户多了memcache的压力会比较大。所以就思考怎么样能够减少对memcache的访问。自己看了一下shiro的源码,只要请求进入了shiro的拦截器链,那么shiro自身在初始化Subject的时候会多次的读取session,那么我们让静态资源的访问不进入shiro的拦截器链,我们可以在项目中覆盖AbstractShiroFilter类,这个是shiro拦截器链的入口程序,代码如果下

package org.apache.shiro.web.servlet;
public abstract class AbstractShiroFilter extends OncePerRequestFilter{
	//只贴出重要的代码 统一把静态资源放在一个文件里,便于好管理
	private static PatternMatcher staticResourcePathMatcher = new AntPathMatcher();
	private static final String staticResourceAnt = "/static/**";
	protected void doFilterInternal(ServletRequest servletRequest,ServletResponse servletResponse,
	,final FilterChain chain){
		Throwable t = null;
		try{
			final ServletRequest request = prepareServletRequest(servletRequest,
			servletResponse,chain);
			final ServletResponse response = prepareServletResponse(servletRequest,
			servletResponse,chain);
			String requestURI = WebUtils.getPathWithinApplication(WebUtil,toHttp(request));
			if(staticResourcePathMatcher.matches(staticResourceAnt,requestURI) ){
				log.debug("过滤静态资源"+requestURI);
				chain.doFilter(request,response);
			}else{
				.......这里就是shiro原生进入拦截器链的入口
			}

		}
	}
}







上面的是对入口的直接过滤,下面我们对进入shiro拦截器链的请求进行处理。
我是写了一个类MySessionDao继承与EnterpriseCacheSessionDAO类,这样就可以把shiro的session用memcache来管理了。减少session的思路主要是在类MySessionDao中也存储一份本地的session,当shiro在读取session的时候,先通过session的id获取本地的,如果没有的话,那么直接读取memcaceh。然后拿本地的session的最后访问时间(lastAccessTime)与当前的时间做一个时间差,如果差在自己设置的范围之后那么读取memcaceh,如果不是的话就返回本地的session。这个时间间隔最好是设置的小一点,如果太大了有可能读取不到最新的session.fdf
shiro在读取session之后它会去更新最后的访问时间,然后调用doUpdate方法更新session,基本上一次请求会更新一次session.为了减少session的更新我想到了以下的操作:session的更新分成两种情况:1.是用户调用了setAttribute方法;2.更新session的最后访问时间。有以上的分析我覆盖了shiro的SimpleSession泪,在它的setAttribute和removeAttribute方法做了一些处理,逻辑代码如下:


public class SimpleSession implements ValidatingSession, Serializable {
      .... 原生代码....
    public static final String SESSION_UPDATE_FALG = "SESSION_UPDATE_FALG";
    public static final String SESSION_TIMESTAMP_FOR_UPDATE = "SESSION_TIMESTAMP_FOR_UPDATE";
    public void setAttribute(Object key, Object value) {
    	//用户设置属性的时候,对当前session设置可以更新标识
        if (value == null) {
            removeAttribute(key);
        } else {
            getAttributesLazy().put(key, value);
        }
        if(!SESSION_UPDATE_FALG.equals(key)&&!SESSION_TIMESTAMP_FOR_UPDATE.equals(key)){
        	getAttributesLazy().put(SESSION_UPDATE_FALG, true);
        }
    }


    public Object removeAttribute(Object key) {
        Map<Object, Object> attributes = getAttributes();
        if (attributes == null) {
            return null;
        } else {
            if(!SESSION_UPDATE_FALG.equals(key)){
            	getAttributesLazy().put(SESSION_UPDATE_FALG, true);
            }
            return attributes.remove(key);
        }
    }
    public void setLastAccessTime(Date lastAccessTime) {
    	if(getAttributes()!=null&&getAttributes().get(SESSION_TIMESTAMP_FOR_UPDATE)==null){
    		getAttributes().put(SESSION_TIMESTAMP_FOR_UPDATE, lastAccessTime);
    		System.out.println("SESSION_TIMESTAMP_FOR_UPDATE-------");
    	}
        this.lastAccessTime = lastAccessTime;
  }
.... 原生代码....
}




在shiro调用doUpdate这方法的时候可以去通过SESSION_UPDATE_FALG这标识获取是否需要更新,
然后再通过这个SESSION_TIMESTAMP_FOR_UPDATE获取更新时间与当前session的最后访问时间做差。
比较自己设置的阈值,
这个可以设置的长一点比如10秒。


package com.unimas.modules.shiro;

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.SimpleSession;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.unimas.freamwork.memcached.CacheUtil;
import com.unimas.util.DateUtils;

public class MySessionDao extends EnterpriseCacheSessionDAO{
	
	private static Logger log = LoggerFactory.getLogger(MySessionDao.class);
	
	public static final String SHIRO_SESSION_FLAG = "shiro_session_flag_";
	
	/**
	 * 系统本地也存储一套用户session
	 */
	static Map<String,Session> sessionCache = new HashMap<String, Session>();
	

    protected Serializable doCreate(Session session) {
        Serializable sessionId = generateSessionId(session);
        assignSessionId(session, sessionId);
        log.debug("创建session id["+session.getId()+",ip["+session.getHost()+"" +
        		",startTime["+DateUtils.DATE_FORMAT_YYYY_MM_DDkgHHmhMMmmss.format( session.getStartTimestamp() )+" ]," +
        		",alstTime["+DateUtils.DATE_FORMAT_YYYY_MM_DDkgHHmhMMmmss.format( session.getLastAccessTime())+"]" +
        		","+session.toString());
        String key = SHIRO_SESSION_FLAG+sessionId.toString();
        long timeOut = session.getTimeout();
        int expire = new Long(timeOut/1000).intValue();
        CacheUtil.add(key, expire, session);
        sessionCache.put(sessionId.toString(), session);
        return sessionId;
    }

    protected Session doReadSession(Serializable sessionId) {
		String key = SHIRO_SESSION_FLAG+sessionId.toString();
		Session session = sessionCache.get(sessionId.toString());
		if(session!=null){
			 Date lastAccessTime = session.getLastAccessTime();
			 long expireTimeMillis = System.currentTimeMillis() - lastAccessTime.getTime();
			 //处理在shiro创建Subjuect时多次读取session的问题。
			 if(expireTimeMillis>600){
				session = (Session)CacheUtil.get(key);
			 }
		}else{
			session = (Session)CacheUtil.get(key);
		}
		if(session!=null){
			//设置本地的访问时间LastAccessTime
			SimpleSession simpleSession = (SimpleSession)session;
			simpleSession.setLastAccessTime(new Date());
			sessionCache.put(sessionId.toString(), session);
		}
		return session;
    }

    protected void doUpdate(Session session) {
    	SimpleSession simpleSession  = (SimpleSession)session;
    	long timeOut = simpleSession.getTimeout();
    	int expire = new Long(timeOut/1000).intValue();
    	String key = SHIRO_SESSION_FLAG+session.getId().toString();
    	Boolean uf = (Boolean)session.getAttribute(SimpleSession.SESSION_UPDATE_FALG);
    	uf = uf==null?false:uf;
    	if(!uf){
    		long cacheSessionLastAccessTime = 0;
    		long currentSessionLastAccessTime = session.getLastAccessTime().getTime();
    		Date cacheSessionLastAccessDate = (Date)simpleSession.getAttribute(SimpleSession.SESSION_TIMESTAMP_FOR_UPDATE);
    		if(cacheSessionLastAccessDate==null){
    			cacheSessionLastAccessTime = currentSessionLastAccessTime;
    		}else{
    			cacheSessionLastAccessTime = cacheSessionLastAccessDate.getTime();
    		}
    		if(currentSessionLastAccessTime-cacheSessionLastAccessTime>10000){
    			session.setAttribute(SimpleSession.SESSION_UPDATE_FALG, false);
    			session.setAttribute(SimpleSession.SESSION_TIMESTAMP_FOR_UPDATE, simpleSession.getLastAccessTime());
    			CacheUtil.put(key, expire, session);
    			sessionCache.put(session.getId().toString(), session);
    		}
    	}else{
			session.setAttribute(SimpleSession.SESSION_TIMESTAMP_FOR_UPDATE, simpleSession.getLastAccessTime());
    		session.setAttribute(SimpleSession.SESSION_UPDATE_FALG, false);
    		CacheUtil.put(key, expire, session);
			sessionCache.put(session.getId().toString(), session);
    	}
    }

    protected void doDelete(Session session) {
    	//删除登陆的队列
    	String key = SHIRO_SESSION_FLAG+session.getId().toString();
    	CacheUtil.delete(key);
    	OnLineUser.deleteOnLineUser(session.getId().toString());
    	sessionCache.remove(key);
    }
	/**
	 * 提供获取session的接口
	 */
	public Session readSession(Serializable sessionId){
		return doReadSession(sessionId);
	}
}