本文在于分析Shiro源码,对于新学习的朋友可以参考
[开涛博客](http://jinnianshilongnian.iteye.com/blog/2018398)进行学习。
本文对Shiro中的SessionManager进行分析,SessionManager用于管理Shiro中的Session信息。Session也就是我们通常说的会话,会话是用户在使用应用程序一段时间内携带的数据。传统的会话一般是基于Web容器(如:Tomcat、EJB环境等)。Shiro提供的Session可以在任何环境中使用,不再依赖于其他容器。
Shiro还提供了一些其他的特性:
- 基于POJO/J2SE:Session和SessionManager都是基于接口实现的,可以通过POJO来进行实现。可以使用任务JavaBean兼容的格式(如:Json,YAML,spring xml或类似机制)来轻松配置所有会话组件。能够根据需要完全地扩展会话组件。
- 会话存储:因为Shiro的Session对象是基于POJO的,所以会话数据可以容易地存储在任何数据源中。 这允许您精确定制应用程序的会话数据所在的位置,例如文件系统,企业缓存,关系数据库或专有数据存储。
- 简单强大的集群:Shiro的Session可以通过缓存来实现集群功能,这样的Session并不会依赖于Web容器,不需要对Web容器进行特殊的配置。
- 事件监听:事件监听器允许在会话生命周期中接受生命周期事件,可以监听这些事件并对自定义应用程序行为做出反应。例如,在会话过期时更新用户记录。
- 主机地址保留:会记录Session创建时的IP地址。
- 会话过期:会话由于预期的不活动而过期,但如果需要,可以通过touch()方法延长活动以保持活动状态。
Session接口定义
Shiro提供的Session和Servlet中的Session其实是一样的作用,只是Shiro中的Sesion不需要再依赖于WEB容器存在。下面是Session接口提供的方法:
Serializable getId();
Date getStartTimestamp();
Date getLastAccessTime();
long getTimeout() throws InvalidSessionException;
void setTimeout(long maxIdleTimeInMillis) throws InvalidSessionException;
String getHost();
void touch() throws InvalidSessionException;
void stop() throws InvalidSessionException;
Collection<Object> getAttributeKeys() throws InvalidSessionException;
Object getAttribute(Object key) throws InvalidSessionException;
void setAttribute(Object key, Object value) throws InvalidSessionException;
Object removeAttribute(Object key) throws InvalidSessionException;
从接口中我们可以看出。Session有一个唯一ID,开始时间,最近活动时间等属性,另外提供了两个相对应的方法touch()和stop(),其他方法就是对属性的操作了。Session接口还是相当简单清晰的,下面我们看看接口的实现类。
Session有一些实现类,包括SimpleSession、HttpServletSession和DelegatingSession等。SimpleSession是Shiro提供的一种简单实现,HttpServletSession是基于Sevlet中Session来实现的,DelegatingSession是一种委托机制,委托给SessionManager来实现。下面,我们主要以SimpleSession和DelegatingSession来分析。
SimpleSession实现
SimpleSession从Session接口继承过来的方法实现非常简单,就不过多分析了。我们主要分析一下属性,通过属性就可以了解到SimpleSession中功能的实现了。
private transient Serializable id;
private transient Date startTimestamp;
private transient Date stopTimestamp;
private transient Date lastAccessTime;
private transient long timeout;
private transient boolean expired;
private transient String host;
private transient Map<Object, Object> attributes;
DelegatingSession实现
DelegatingSession是一种委托实现方式,所有的操作都委托给SessionManager接口来实现。我们还是先看有哪些属性。
private final SessionKey key;
private final transient NativeSessionManager sessionManager;
很显然,DelegatingSession的委托方式是用过SessionKey从SessionManager中获取相应的Session来进行处理的。在SessionManager中管理着很多Session。
在分析SessionManager前,先说明一下SessionContext这个接口(上一篇中提到过 )。SessionContext表示的是Session创建时的上下文参数,SessionContext有DefaultSessionContext,DefaultWebSessionContext两个实现。但功能都是从MapContext继承来,简单地说SessionContext就是一个Map对象,提供了一个更方便获取具体类型的方法getTypedValue(String key, Class<E> type)。
SessionManager分析
SessionManager管理着Session的创建、操作以及清除等。SessionManager有一些子接口,包括NativeSessionManager、ValidatingSessionManager和WebSessionManager。每个接口都提供了相关的抽象类AbstractSessionManager、AbstractNativeSessionManager、AbstractValidatingSessionManager。我们还是先看看接口提供了哪些方法。
public interface SessionManager {
Session start(SessionContext context);
Session getSession(SessionKey key) throws SessionException;
}
public interface NativeSessionManager extends SessionManager {
Date getStartTimestamp(SessionKey key);
Date getLastAccessTime(SessionKey key);
boolean isValid(SessionKey key);
void checkValid(SessionKey key) throws InvalidSessionException;
long getTimeout(SessionKey key) throws InvalidSessionException;
void setTimeout(SessionKey key, long maxIdleTimeInMillis) throws InvalidSessionException;
void touch(SessionKey key) throws InvalidSessionException;
String getHost(SessionKey key);
void stop(SessionKey key) throws InvalidSessionException;
Collection<Object> getAttributeKeys(SessionKey sessionKey);
Object getAttribute(SessionKey sessionKey, Object attributeKey) throws InvalidSessionException;
void setAttribute(SessionKey sessionKey, Object attributeKey, Object value) throws InvalidSessionException;
Object removeAttribute(SessionKey sessionKey, Object attributeKey) throws InvalidSessionException;
}
public interface ValidatingSessionManager extends SessionManager {
void validateSessions();
}
从上面的接口中可以看出,SessionManager主要负责创建Session和获取Session,NativeSessionManager接口中包含了所有对Session的操作,这些操作方法和Session接口中是一致的,而ValidatingSessionManager接口提供了对Session校验的支持。这些接口从功能上分工很明确。
AbstractNativeSessionManager分析
AbstractNativeSessionManager类对NativeSessionManager接口做了一个整体的结构实现,定型了整个接口的实现基础。我们先列出AbstractNativeSessionManager中主要功能:
- 引用了SessionListen接口来负责对Session状态的监听。
- 提供了创建Session的抽象方法createSession(SessionContext context)和获取Session的抽象方法doGetSession(SessionKey key),这两个方法都应该从SessionManager接口来实现的。
- 提供了onChange,onStart,onStop,afterStopped钩子方法。
我们先分析SessionManager中的两个方法。start(SessionContext context)和getSession(SessionKey key)。
public Session start(SessionContext context) {
Session session = createSession(context);
applyGlobalSessionTimeout(session);
onStart(session, context);
notifyStart(session);
return createExposedSession(session, context);
}
protected Session createExposedSession(Session session, SessionContext context) {
return new DelegatingSession(this, new DefaultSessionKey(session.getId()));
}
public Session getSession(SessionKey key) throws SessionException {
Session session = lookupSession(key);
return session != null ? createExposedSession(session, key) : null;
}
private Session lookupSession(SessionKey key) throws SessionException {
if (key == null) {
throw new NullPointerException("SessionKey argument cannot be null.");
}
return doGetSession(key);
}
接下来再看看Session监听器的方法。监听器提供了对Session状态的监听:Session启动,Session停止,Session过期。
protected void notifyStart(Session session) {
for (SessionListener listener : this.listeners) {
listener.onStart(session);
}
}
protected void notifyStop(Session session) {
Session forNotification = beforeInvalidNotification(session);
for (SessionListener listener : this.listeners) {
listener.onStop(forNotification);
}
}
protected void notifyExpiration(Session session) {
Session forNotification = beforeInvalidNotification(session);
for (SessionListener listener : this.listeners) {
listener.onExpiration(forNotification);
}
}
最后,我们再看看对NativeSessionManager接口方法的实现,方法很多,基本上实现思路一样。我们以touch和stop来说明。
public void touch(SessionKey key) throws InvalidSessionException {
Session s = lookupRequiredSession(key);
s.touch();
onChange(s);
}
public void stop(SessionKey key) throws InvalidSessionException {
Session session = lookupRequiredSession(key);
try {
if (log.isDebugEnabled()) {
log.debug("Stopping session with id [" + session.getId() + "]");
}
session.stop();
onStop(session, key);
notifyStop(session);
} finally {
afterStopped(session);
}
}
我们可以给AbstractNativeSessionManager类作一个总结。该类负责管理Session的操作,但操作的具体实现是由Session自己实现的。相当于对Session操作前后做代理(不管是提供钩子方法还是监听Session)。
AbstractValidatingSessionManager分析
AbstractValidatingSessionManager的作用是定期的校验所有有效的Session状态,因为Session可能被停止或过期。AbstractValidatingSessionManager类继承了上面分析的AbstractNativeSessionManager,然后实现了ValidatingSessionManager接口中的validateSessions()方法。我们还是先从属性开始分析,下面是AbstractValidatingSessionManager类中的属性。
protected boolean sessionValidationSchedulerEnabled;
protected SessionValidationScheduler sessionValidationScheduler;
protected long sessionValidationInterval;
AbstractValidatingSessionManager定义了一些和调度相关的属性,我们先了解一下SessionValidationScheduler接口有哪些方法。SessionValidationScheduler提供了3个方法:
- isEnabled() - 表示是否已经开始了调度作业
- enableSessionValidation() - 开启具体调度的作业内容
- disableSessionValidation() - 停止调度作业
那么,我们应该想想,调度的作业内容是什么?很显然,这个作业内容是校验Session相关的事情,也就是为什么在ValidatingSessionManager接口中提供了validateSessions()方法的原因。
明白了SessionValidationScheduler接口之后,我们在回过来分析AbstractValidatingSessionManager就相当容易了。我们先看看是如何校验的,分析enableSessionValidationIfNecessary()方法。
private void enableSessionValidationIfNecessary() {
SessionValidationScheduler scheduler = getSessionValidationScheduler();
if (isSessionValidationSchedulerEnabled() && (scheduler == null || !scheduler.isEnabled())) {
enableSessionValidation();
}
}
protected void enableSessionValidation() {
SessionValidationScheduler scheduler = getSessionValidationScheduler();
if (scheduler == null) {
scheduler = createSessionValidationScheduler();
setSessionValidationScheduler(scheduler);
}
scheduler.enableSessionValidation();
afterSessionValidationEnabled();
}
protected SessionValidationScheduler createSessionValidationScheduler() {
ExecutorServiceSessionValidationScheduler scheduler;
scheduler = new ExecutorServiceSessionValidationScheduler(this);
scheduler.setInterval(getSessionValidationInterval());
return scheduler;
}
上面我们只是从代码片段上分析了如何开启校验的,现在我们继续跟随AbstractNativeSessionManager的分析。我们说在AbstractNativeSessionManager中已经定型了整个类的基本实现,提供了两个抽象方法createSession(SessionContext context)和doGetSession(SessionKey key)。在AbstractValidatingSessionManager中对这两个方法进行了实现。下面主要分析这两个方法是怎么实现的。
protected Session createSession(SessionContext context) throws AuthorizationException {
enableSessionValidationIfNecessary();
return doCreateSession(context);
}
protected abstract Session doCreateSession(SessionContext initData) throws AuthorizationException;
protected final Session doGetSession(final SessionKey key) throws InvalidSessionException {
enableSessionValidationIfNecessary();
Session s = retrieveSession(key);
if (s != null) {
validate(s, key);
}
return s;
}
protected void validate(Session session, SessionKey key) throws InvalidSessionException {
try {
doValidate(session);
} catch (ExpiredSessionException ese) {
onExpiration(session, ese, key);
throw ese;
} catch (InvalidSessionException ise) {
onInvalidation(session, ise, key);
throw ise;
}
}
protected void doValidate(Session session) throws InvalidSessionException {
if (session instanceof ValidatingSession) {
((ValidatingSession) session).validate();
} else {
String msg = "The " + getClass().getName() + " implementation only supports validating " +
"Session implementations of the " + ValidatingSession.class.getName() + " interface. " +
"Please either implement this interface in your session implementation or override the " +
AbstractValidatingSessionManager.class.getName() + ".doValidate(Session) method to perform validation.";
throw new IllegalStateException(msg);
}
}
从上面的分析可以得出:AbstractValidatingSessionManager类负责校验Session,对于如何创建和获取Session,并不是它的需要处理的任务。另外AbstractValidatingSessionManager还实现了Destroyable接口,表示销毁时应该处理销毁功能。
protected void disableSessionValidation() {
beforeSessionValidationDisabled();
SessionValidationScheduler scheduler = getSessionValidationScheduler();
if (scheduler != null) {
try {
scheduler.disableSessionValidation();
} catch (Exception e) {
if (log.isDebugEnabled()) {
String msg = "Unable to disable SessionValidationScheduler. Ignoring (shutting down)...";
log.debug(msg, e);
}
}
LifecycleUtils.destroy(scheduler);
setSessionValidationScheduler(null);
}
}
最后,还有一个重要的功能。上面说到过,调度任务处理的内容是什么?也就是我们说的ValidatingSessionManager接口提供的validateSessions()方法。
public void validateSessions() {
int invalidCount = 0;
Collection<Session> activeSessions = getActiveSessions();
if (activeSessions != null && !activeSessions.isEmpty()) {
for (Session s : activeSessions) {
try {
SessionKey key = new DefaultSessionKey(s.getId());
validate(s, key);
} catch (InvalidSessionException e) {
invalidCount++;
}
}
}
if (log.isInfoEnabled()) {
if (invalidCount > 0) {
msg += " [" + invalidCount + "] sessions were stopped.";
} else {
msg += " No sessions were stopped.";
}
log.info(msg);
}
}
同样,我们也可以给AbstractValidatingSessionManager来总结一下。AbstractValidatingSessionManager类会启动调度作业来校验Session,而调度作业的真正内容是检测每个Session的validate()方法,Session必须是ValidatingSession类型。validate()方法会抛出两个异常StoppedSessionException和ExpiredSessionException异常,这个两个异常都是InvalidSessionException的子类,所以抛出异常的时候会处理onExpiration()或onInvalidation()方法。
DefaultSessionManager分析
上面分析的都是抽象类,抽象类只是提供了一个基础的框架,在Shiro中DefaultSessionManager才是我们所使用的SessionManager接口的具体实现类。在分析AbstractValidatingSessionManager的时候,我们说过对于创建和获取Session,并不是它的职责。Session如何创建的?Session存放在哪里?我们都还不清楚。在DefaultSessionManager中我们将会知道Shiro是如何做的。还是按照习惯的方式,先看看有哪些属性和构造方法。
private SessionFactory sessionFactory;
protected SessionDAO sessionDAO;
private CacheManager cacheManager;
private boolean deleteInvalidSessions;
public DefaultSessionManager() {
this.deleteInvalidSessions = true;
this.sessionFactory = new SimpleSessionFactory();
this.sessionDAO = new MemorySessionDAO();
}
关于如何创建Session的,放在后面说。上面我们说了抛出异常的时候会处理onExpiration()或onInvalidation()方法。继续讨论检测Session无效后是怎么处理的,下面的方法就是处理Session的实现,如果Session被检测出被停止或过期就会调用相应的方法处理,返回将无效的Session删除(如果参数配置需要删除的话)或将Session更新。
@Override
protected void onStop(Session session) {
if (session instanceof SimpleSession) {
SimpleSession ss = (SimpleSession) session;
Date stopTs = ss.getStopTimestamp();
ss.setLastAccessTime(stopTs);
}
onChange(session);
}
@Override
protected void afterStopped(Session session) {
if (isDeleteInvalidSessions()) {
delete(session);
}
}
protected void onExpiration(Session session) {
if (session instanceof SimpleSession) {
((SimpleSession) session).setExpired(true);
}
onChange(session);
}
@Override
protected void afterExpired(Session session) {
if (isDeleteInvalidSessions()) {
delete(session);
}
}
protected void onChange(Session session) {
sessionDAO.update(session);
}
SessionDAO CRUD操作
Session的创建是由SessionFactory来实现的。Shiro只提供了SimpleSessionFactory一个实现类,创建SimpleSession实例。如果需要则可以根据业务扩展接口。创建Session后会调用SessionDAO#create(session)方法,将Session存储起来。对Session的CRUD操作都是通过SessionDAO来处理的,SessionDAO负责将Session存储在哪里,怎么存储。下面简单地描述一下SessionDAO接口。
public interface SessionDAO {
Serializable create(Session session);
Session readSession(Serializable sessionId) throws UnknownSessionException;
void update(Session session) throws UnknownSessionException;
void delete(Session session);
Collection<Session> getActiveSessions();
}
Shiro提供了两种SessionDAO,第一种是MemorySessionDAO,它将Session存储在内存中;第二种是EnterpriseCacheSessionDAO,可以定义将Session存入到缓存中。由于在使用中我们很大可能需要自定义SessionDAO,下面对SessionDAO也展开分析。MemorySessionDAO是以Map作为存储的,很简单不再说明。我们以EnterpriseCacheSessionDAO来分析。EnterpriseCacheSessionDAO类的继承关系是这样的:EnterpriseCacheSessionDAO->CachingSessionDAO->AbstractSessionDAO。在AbstractSessionDAO中提供了SessionIdGenerator类型的属性,用于生成Session唯一ID值。我们需要分析的重点是CachingSessionDAO。
CachingSessionDAO分析
CachingSessionDAO是一个抽象类,负责对Session进行缓存管理,所有Session都存入到一个缓存中。Shiro提供自己的Cache和CacheManager两个接口。CacheManger负责管理Cache对象实例。下面是CachingSessionDAO的属性,我们可以看出Session就是存放在activeSessions中。
public static final String ACTIVE_SESSION_CACHE_NAME = "shiro-activeSessionCache";
private CacheManager cacheManager;
private Cache<Serializable, Session> activeSessions;
private String activeSessionsCacheName = ACTIVE_SESSION_CACHE_NAME;
看看在CachingSessionDAO是怎样来创建Session,获取Session,更新Session的。
public Serializable create(Session session) {
Serializable sessionId = super.create(session);
cache(session, sessionId);
return sessionId;
}
public Session readSession(Serializable sessionId) throws UnknownSessionException {
Session s = getCachedSession(sessionId);
if (s == null) {
s = super.readSession(sessionId);
}
return s;
}
public void update(Session session) throws UnknownSessionException {
doUpdate(session);
if (session instanceof ValidatingSession) {
if (((ValidatingSession) session).isValid()) {
cache(session, session.getId());
} else {
uncache(session);
}
} else {
cache(session, session.getId());
}
}
public void delete(Session session) {
uncache(session);
doDelete(session);
}
总结
在本篇中我们了解了Session、SessionManager以及SessionDAO。Session表示用户的会话数据,Session的核心点是状态,Session有无效和活动两种状态。其中,无效又包括被停止和过期状态,我们可以对Session状态进行监听和检测。
另外,就是Session的存储,在Shiro中使用SessionDAO接口来处理Session的存储,Shiro中提供了基于内存和基于缓存的两种方式来存储Session。