项目开发用到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);
}
}