Shiro源码分析(2) - 会话管理器(SessionManager)

本文在于分析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接口提供的方法:

/**
 * 返回表示Session的唯一ID
 */
Serializable getId();
/**
 * 返回Session的开始时间
 */
Date getStartTimestamp();
/**
 * 返回最近使用的时间
 */
Date getLastAccessTime();
/**
 * 返回还有多久Session过期(毫秒)
 */
long getTimeout() throws InvalidSessionException;
/**
 * 设置超时时间(毫秒)
 */
void setTimeout(long maxIdleTimeInMillis) throws InvalidSessionException;    
/**
 * 返回Session创建时的主机名或地址
 */
String getHost();
/**
 * 更新最近访问时间,确保Session不会过期
 */
void touch() throws InvalidSessionException;
/**
 * 设置Session停止使用,并释放相关资源
 */
void stop() throws InvalidSessionException;
/**
 * 返回该Session存储的所有属性键
 */
Collection<Object> getAttributeKeys() throws InvalidSessionException;
/**
 * 获取Session属性
 */
Object getAttribute(Object key) throws InvalidSessionException;
/**
 * 设置Session属性
 */
void setAttribute(Object key, Object value) throws InvalidSessionException;
/**
 * 删除Session属性
 */
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中功能的实现了。

// Session唯一ID
private transient Serializable id;
// 开始时间
private transient Date startTimestamp;
// 结束时间
private transient Date stopTimestamp;
// 最近访问时间
private transient Date lastAccessTime;
// 还有多久过期
private transient long timeout;
// 是否过期
private transient boolean expired;
// Session创建时的主机名
private transient String host;
// Session存储的属性值
private transient Map<Object, Object> attributes;

DelegatingSession实现

DelegatingSession是一种委托实现方式,所有的操作都委托给SessionManager接口来实现。我们还是先看有哪些属性。

// SessionKey就是Session唯一ID的对象形式
private final SessionKey key;

// 委派给NativeSessionManager对象,这是SessionManager接口的一个子接口
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,context提供一些初始化的数据
     */
    Session start(SessionContext context);
    /**
     * 通过SessionKey查找Session
     * 如果存在则被找到,如果不存在则返回null;如果找到的Session无效(被停止或过期)则会抛异常
     */
    Session getSession(SessionKey key) throws SessionException;
}   
public interface NativeSessionManager extends SessionManager {
    /**
     * 返回Session被开启的时间
     */
    Date getStartTimestamp(SessionKey key);
    /**
     * 获取Session最近访问的时间
     */
    Date getLastAccessTime(SessionKey key);
    /**
     * 判断Session是否有效
     */
    boolean isValid(SessionKey key);
    /**
     * 检测Session是否有效,如果无效则抛出异常
     */
    void checkValid(SessionKey key) throws InvalidSessionException;
    /**
     * 返回Session还有多久过期(毫秒)
     * 如果是负数,表示Session不会过期;如果是正数,表示Session在这个时间后就会过期。
     * 如果Session无效调用这个方法会抛异常
     */
    long getTimeout(SessionKey key) throws InvalidSessionException;
    /**
     * 设置过期时间(毫秒),设置负数表示Session不会过期。
     * 如果Session无效调用这个方法会抛异常
     */
    void setTimeout(SessionKey key, long maxIdleTimeInMillis) throws InvalidSessionException;
    /**
     * 会设置最近访问时间,确保Session没有超时,当然,如果Session已经无效也会抛异常
     */
    void touch(SessionKey key) throws InvalidSessionException;
    /**
     * 返回主机名或Ip
     */
    String getHost(SessionKey key);
    /**
     * 停用Session,释放资源
     */
    void stop(SessionKey key) throws InvalidSessionException;
    /**
     * 返回Session中存储的所有属性键
     */
    Collection<Object> getAttributeKeys(SessionKey sessionKey);
    /**
     * 获取Session中的属性
     */
    Object getAttribute(SessionKey sessionKey, Object attributeKey) throws InvalidSessionException;
    /**
     * 设置Session中的属性
     */
    void setAttribute(SessionKey sessionKey, Object attributeKey, Object value) throws InvalidSessionException;
    /**
     * 删除Session中的属性
     */
    Object removeAttribute(SessionKey sessionKey, Object attributeKey) throws InvalidSessionException;
}
public interface ValidatingSessionManager extends SessionManager {
    /**
     * 为那些有效的Session进行校验,如果Session发现是无效的,将会被更改。
     * 这个方法期望有规律的运行,如1小时一次,一天一次或一星期一次。运行的频率取决于应用的性能,用户活跃数等。
     */
    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 session = createSession(context);
    applyGlobalSessionTimeout(session);
    // 钩子方法,子类实现
    onStart(session, context);
    // Session监听器
    notifyStart(session);
    // 使用DelegatingSession来委托SessionManger处理
    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
    Session s = lookupRequiredSession(key);
    // 调用Session自己提供的功能
    s.touch();
    // 调用onChange方法作为变更后的后置处理方法
    onChange(s);
}
public void stop(SessionKey key) throws InvalidSessionException {
    // 获取Session
    Session session = lookupRequiredSession(key);
    try {
        if (log.isDebugEnabled()) {
            log.debug("Stopping session with id [" + session.getId() + "]");
        }
        // 调用Session自己提供的功能
        session.stop();
        // 调用后置处理方法
        onStop(session, key);
        // 通知监听器
        notifyStop(session);
    } finally {
        // 调用停止后的后置方法
        afterStopped(session);
    }
}

我们可以给AbstractNativeSessionManager类作一个总结。该类负责管理Session的操作,但操作的具体实现是由Session自己实现的。相当于对Session操作前后做代理(不管是提供钩子方法还是监听Session)。


AbstractValidatingSessionManager分析

AbstractValidatingSessionManager的作用是定期的校验所有有效的Session状态,因为Session可能被停止或过期。AbstractValidatingSessionManager类继承了上面分析的AbstractNativeSessionManager,然后实现了ValidatingSessionManager接口中的validateSessions()方法。我们还是先从属性开始分析,下面是AbstractValidatingSessionManager类中的属性。

// 标识是否需要校验Sesion
protected boolean sessionValidationSchedulerEnabled;
// 从名称上看,我们就知道这个一个调度器。用来定时调度校验Session
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();
    // 首先,判断是否需要校验Session,如果为false,则永远不会开启校验,根本就不判断scheduler的状态;
    // 其次,如果需要校验Session,会去判断scheduler== null或scheduler没有启动,在这两种情况下都会去开启调度任务
    // 也就是说,如果任务没有开启就去开启,如果已经开启了,就不会在处理。
    if (isSessionValidationSchedulerEnabled() && (scheduler == null || !scheduler.isEnabled())) {
        enableSessionValidation();
    }
}
/**
 * 具体的开启实现在这里
 **/
protected void enableSessionValidation() {
    // 是否设置了scheduler,如果没有就创建一个
    SessionValidationScheduler scheduler = getSessionValidationScheduler();
    if (scheduler == null) {
        // 创建scheduler
        scheduler = createSessionValidationScheduler();
        setSessionValidationScheduler(scheduler);
    }
    // 开启调度作业
    scheduler.enableSessionValidation();
    // 开启作业后的后置处理方法(钩子方法)
    afterSessionValidationEnabled();
}
/**
 * 创建scheduler
 **/
protected SessionValidationScheduler createSessionValidationScheduler() {
    ExecutorServiceSessionValidationScheduler scheduler;
    // 注意:这个的this指的就是ValidatingSessionManager接口啦,前面分析过,调度作业的具体内容时由这个接口来提供的。
    scheduler = new ExecutorServiceSessionValidationScheduler(this);
    scheduler.setInterval(getSessionValidationInterval());
    return scheduler;
}

上面我们只是从代码片段上分析了如何开启校验的,现在我们继续跟随AbstractNativeSessionManager的分析。我们说在AbstractNativeSessionManager中已经定型了整个类的基本实现,提供了两个抽象方法createSession(SessionContext context)和doGetSession(SessionKey key)。在AbstractValidatingSessionManager中对这两个方法进行了实现。下面主要分析这两个方法是怎么实现的。

// 实现createSession方法
protected Session createSession(SessionContext context) throws AuthorizationException {
    // 这就是上面分析过的方法,判断是否需要校验和启动调度作业
    enableSessionValidationIfNecessary();
    // 继续提供抽象方法由子类实现怎么创建Session
    return doCreateSession(context);
}
protected abstract Session doCreateSession(SessionContext initData) throws AuthorizationException;
// 实现doGetSession方法
protected final Session doGetSession(final SessionKey key) throws InvalidSessionException {
    // 这就是上面分析过的方法,判断是否需要校验和启动调度作业
    enableSessionValidationIfNecessary();
    // 继续提供抽象方法有子类实现怎么获取Session
    Session s = retrieveSession(key);
    if (s != null) {
        // 验证Session:
        // 首先,必须是ValidatingSession接口类型的Session
        // 其次,验证Session是否过期
        // 最后,验证Session是否无效
        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);
        // 重置scheduler为null
        setSessionValidationScheduler(null);
    }
}

最后,还有一个重要的功能。上面说到过,调度任务处理的内容是什么?也就是我们说的ValidatingSessionManager接口提供的validateSessions()方法。

public void validateSessions() {
    // 标志无效的Session个数
    int invalidCount = 0;
    // 获取所有有效的Session
    // 这是一个抽象方法。因为目前来说,也不知道Session存放在哪里,要从哪里获取呢?
    Collection<Session> activeSessions = getActiveSessions();
    // 遍历判断Session是否有效
    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是如何做的。还是按照习惯的方式,先看看有哪些属性和构造方法。

// 从名称上看就知道这个创建Session的工厂接口
private SessionFactory sessionFactory;
// DAO是做数据存储的,所以SessionDAO负责Session的CRUD操作
protected SessionDAO sessionDAO;
// 缓存管理器,这个很好理解,Session是频繁使用的对象,需要采用缓存功能
private CacheManager cacheManager;
// 是否删除无效的Session
private boolean deleteInvalidSessions;
// 默认构造方法
public DefaultSessionManager() {
    // 删除无效Session
    this.deleteInvalidSessions = true;
    // SimpleSessionFactory工厂创建SimpleSession实例
    this.sessionFactory = new SimpleSessionFactory();
    // 用内存存储Session
    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 {
    /**
     * 插入Session(可以是数据库,文件系统,内存,缓存等)
     */
    Serializable create(Session session);
    /**
     * 获取Session
     */
    Session readSession(Serializable sessionId) throws UnknownSessionException;
    /**
     * 更新Session
     */
    void update(Session session) throws UnknownSessionException;
    /**
     * 删除Session
     */
    void delete(Session 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中。

/**
 * 默认Session缓存名称
 */
public static final String ACTIVE_SESSION_CACHE_NAME = "shiro-activeSessionCache";
/**
 * 缓存管理器
 */
private CacheManager cacheManager;
/**
 * 存储Session的缓存对象
 */
private Cache<Serializable, Session> activeSessions;
/**
 * Session缓存名
 */
private String activeSessionsCacheName = ACTIVE_SESSION_CACHE_NAME;

看看在CachingSessionDAO是怎样来创建Session,获取Session,更新Session的。

// 接口方法,创建Session
public Serializable create(Session session) {
    Serializable sessionId = super.create(session);
    // 相当于对父类方法做了后置缓存处理
    // 实际上就是调用Cache#put()方法,将Session存储
    cache(session, sessionId);
    return sessionId;
}
// 实现父类抽象方法,获取Session
public Session readSession(Serializable sessionId) throws UnknownSessionException {
    // 相当于对父类方法做了前置处理
    // 先从缓存中获取,如果缓存中没有再调用真实方法
    Session s = getCachedSession(sessionId);
    if (s == null) {
        s = super.readSession(sessionId);
    }
    return s;
}
// 修改Session
public void update(Session session) throws UnknownSessionException {
    // 抽象方法,修改Session前置处理
    doUpdate(session);
    // 将Session更新到缓存中
    if (session instanceof ValidatingSession) {
        if (((ValidatingSession) session).isValid()) {
            cache(session, session.getId());
        } else {
            uncache(session);
        }
    } else {
        cache(session, session.getId());
    }
}
// 删除Session
public void delete(Session session) {
    // 从缓存中删除
    uncache(session);
    // 删除后置方法
    doDelete(session);
}

总结

在本篇中我们了解了Session、SessionManager以及SessionDAO。Session表示用户的会话数据,Session的核心点是状态,Session有无效和活动两种状态。其中,无效又包括被停止和过期状态,我们可以对Session状态进行监听和检测。

另外,就是Session的存储,在Shiro中使用SessionDAO接口来处理Session的存储,Shiro中提供了基于内存和基于缓存的两种方式来存储Session。

猜你喜欢

转载自blog.csdn.net/qh870754310/article/details/83057807