Shiro提供了缓存功能,以确保安全操作保持尽可能的快。
但是Shiro作为一个安全框架,不可能实现一个完整的缓存机制,因此,Shiro提供了一个抽象的缓存API,我们可以通过此套API来继承任何的缓存产品。
首先我们来看看Shiro的缓存API的接口:
public interface Cache<K,V> {
public V get(K key) throws CacheException;//获取指定的键值(或null)对应的缓存实例,访问底仓缓存系统异常时抛出CacheException
public V put(K key, V value) throws CacheException;//添加缓存实例
public V remove(K key) throws CacheException;//删除指定的键值对应的缓存实例
public void clear() throws CacheException;//清空全部缓存
public int size();//获取缓存的数量
public Set<K> keys();//返回所有缓存的键值集合
public Collection<V> values();//获取所有缓存实例的集合
}
/**
* 缓存管理器
*/
public interface CacheManager {
// 根据指定的缓存名称获取对应的缓存实例。如果缓存实例不存在,则新建一个。
<K, V> Cache<K,V> getCache(String name) throws CacheException;
}
public interface CacheManagerAware {
//注入可用的CacheManager实例
void setCacheManager(CacheManager cacheManager);
}
Shiro通过以上三个接口,使得扩展缓存机制变得非常容易,只要实现Cache,用来从缓存中增删改查数据,
然后实现CacheManager 来创建Cache对象就可以使用。
除了定义了以上三个接口,Shiro-core还提供了基于软引用来使JVM自动回收的缓存机制
public class MemoryConstrainedCacheManager extends AbstractCacheManager {
@Override
protected Cache createCache(String name) {
return new MapCache<Object,Object>(name, new SoftHashMap<Object, Object>());
}
}
SoftHashMap使用软引用来存放Map的 value,并在每次调用时清空 已经被垃圾收集器回收的Value对应的key。
接下来我们写个测试案例:
public class CacheService implements CacheManagerAware {
private final String CACHE_NAME = getClass().getName()+".CACHE_NAME";
private CacheManager cacheManager;
private Datebase datebase = new Datebase();
public CacheService() {
super();
this.cacheManager = new MemoryConstrainedCacheManager();
}
@Override
public void setCacheManager(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
public Object getValue(String key){
Object value = null;
//先从缓存中获取
if(cacheManager != null){
value = cacheManager.getCache(CACHE_NAME).get(key);
}
//没有则从Datebase中获取
if(value == null){
value = datebase.getValue(key);
cacheManager.getCache(CACHE_NAME).put(key, value);
}
return value;
}
}
class Datebase{
private Map<String , Object> resMap = new HashMap<>();
public Datebase(){
resMap.put("1", "admin");
resMap.put("2", "张三");
resMap.put("3", Math.PI);
resMap.put("5", resMap);
}
public Object getValue(String key){
System.out.println("从 Datebase中获取Value:"+key);
return resMap.get(key);
}
}
public static void main(String[] args) {
CacheService cacheService = new CacheService();
System.out.println(cacheService.getValue("3"));
System.out.println(cacheService.getValue("5"));
System.out.println(cacheService.getValue("3"));
System.out.println(cacheService.getValue("1"));
}
输出结果:
从 Datebase中获取Value:3
3.141592653589793
从 Datebase中获取Value:5
{1=admin, 2=张三, 3=3.141592653589793, 5=(this Map)}
3.141592653589793
从 Datebase中获取Value:1
admin
可以看到再次查询 键值为 3 的数据时,没有直接访问Datebase.
除了以上基于软引用和内存回收机制的缓存,Shiro还为我们提供了基于EhCache的缓存扩展。只需要我们引入对应的包,并注入CacheManager就可以使用。
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.3.2</version>
</dependency>
接下来我们看看Shiro中对缓存的支持。
查看实现了CacheManagerAware 的类与他们的继承关系,可以看到支持缓存的组件有Realm, SecurityManager, SessionDAO, SessionManager。
先看看Realm对于身份认证的缓存:
CachingRealm
public abstract class CachingRealm implements Realm, Nameable, CacheManagerAware, LogoutAware {
private boolean cachingEnabled; //是否开启缓存
private CacheManager cacheManager; //缓存管理器
// getter 、setter 、一些钩子方法
}
AuthenticatingRealm
public abstract class AuthenticatingRealm extends CachingRealm implements Initializable {
//存放缓存数据的缓存实例
private Cache<Object, AuthenticationInfo> authenticationCache;
//是否开启身份认证缓存
private boolean authenticationCachingEnabled;
//缓存的名称,在CacheManager中的唯一key.
private String authenticationCacheName;
public AuthenticatingRealm(CacheManager cacheManager, CredentialsMatcher matcher) {
//省略……
//默认不开启缓存
this.authenticationCachingEnabled = false;
//省略……
//缓存名称
this.authenticationCacheName = getClass().getName() + DEFAULT_AUTHORIZATION_CACHE_SUFFIX;
//省略……
if (cacheManager != null) {
setCacheManager(cacheManager);
}
//……
}
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//从缓存中获取 AuthenticationInfo , 可以理解为从 字段 authenticationCache中获取。
AuthenticationInfo info = getCachedAuthenticationInfo(token);
//获取不到,调用子类的 doGetAuthenticationInfo(token)实现。
if (info == null) {
//otherwise not cached, perform the lookup:
info = doGetAuthenticationInfo(token);
log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
if (token != null && info != null) {
cacheAuthenticationInfoIfPossible(token, info);
}
} else {
log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
}
if (info != null) {
assertCredentialsMatch(token, info);
} else {
log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
}
return info;
}
//从缓存中获取 AuthenticationInfo
private AuthenticationInfo getCachedAuthenticationInfo(AuthenticationToken token) {
AuthenticationInfo info = null;
//获取缓存实例 Cache,可以认为是上面的字段 authenticationCache
Cache<Object, AuthenticationInfo> cache = getAvailableAuthenticationCache();
//从缓存中获取 AuthenticationInfo
if (cache != null && token != null) {
log.trace("Attempting to retrieve the AuthenticationInfo from cache.");
Object key = getAuthenticationCacheKey(token);
info = cache.get(key);
if (info == null) {
log.trace("No AuthorizationInfo found in cache for key [{}]", key);
} else {
log.trace("Found cached AuthorizationInfo for key [{}]", key);
}
}
return info;
}
//获取缓存实例,可以认为是上面的字段 authenticationCache
private Cache<Object, AuthenticationInfo> getAvailableAuthenticationCache() {
Cache<Object, AuthenticationInfo> cache = getAuthenticationCache(); //字段中的authenticationCache
// 判断缓存是否开启, 此处判断了 authenticationCachingEnabled 和从父类继承的cachingEnabled,都为true时,缓存开启
boolean authcCachingEnabled = isAuthenticationCachingEnabled();
if (cache == null && authcCachingEnabled) {
// 如果缓存实例 为null, 从 CacheManager中获取
cache = getAuthenticationCacheLazy();
}
return cache;
}
}
以上代码显示了Realm 获取 AuthenticationInfo 回先尝试从CacheManager中获取, 获取不到才调用 doGetAuthenticationInfo(token)从数据源获取。
查看 DefaultSessionManager 的代码可以看到CacheManager主要用于设置 SessionDAO 的CacheManager,这里并未直接使用缓存。
接着查看SessionDAO, 查看源码可以看到在新增、修改Session时,会缓存Session, 删除时会清除缓存。读取Session会优先从缓存读取。
对于 SecurityManager,我们查看源码,可以得知主要是用于设置 Realm的 CacheManager.
public abstract class CachingSecurityManager implements SecurityManager, Destroyable, CacheManagerAware, EventBusAware {
public void setCacheManager(CacheManager cacheManager) {
this.cacheManager = cacheManager;
afterCacheManagerSet();
}
}
public abstract class RealmSecurityManager extends CachingSecurityManager {
protected void afterCacheManagerSet() {
super.afterCacheManagerSet();
applyCacheManagerToRealms();
}
protected void applyCacheManagerToRealms() {
CacheManager cacheManager = getCacheManager();
Collection<Realm> realms = getRealms();
if (cacheManager != null && realms != null && !realms.isEmpty()) {
for (Realm realm : realms) {
if (realm instanceof CacheManagerAware) {
((CacheManagerAware) realm).setCacheManager(cacheManager);
}
}
}
}
}
public abstract class SessionsSecurityManager extends AuthorizingSecurityManager {
protected void afterCacheManagerSet() {
super.afterCacheManagerSet();
applyCacheManagerToSessionManager();
}
protected void applyCacheManagerToSessionManager() {
if (this.sessionManager instanceof CacheManagerAware) {
((CacheManagerAware) this.sessionManager).setCacheManager(getCacheManager());
}
}
}
因此,我们只需要设置SecurityManager的CacheManager,系统中的SessionManager 和 Realm都会使用此缓存机制。
在Realm中
public abstract class AuthenticatingRealm extends CachingRealm implements Initializable {
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//首先尝试从缓存中获取
AuthenticationInfo info = getCachedAuthenticationInfo(token);
//缓存中获取不到,再调用我们实现的从数据源中获取
if (info == null) {
info = doGetAuthenticationInfo(token);
if (token != null && info != null) {
cacheAuthenticationInfoIfPossible(token, info);
}
}
//省略……
return info;
}
private AuthenticationInfo getCachedAuthenticationInfo(AuthenticationToken token) {
AuthenticationInfo info = null;
//获取缓存实例,如果字段中的缓存实例为null,从cacheManager中获取
Cache<Object, AuthenticationInfo> cache = getAvailableAuthenticationCache();
//从缓存中获取 AuthenticationInfo
if (cache != null && token != null) {
Object key = getAuthenticationCacheKey(token);
info = cache.get(key);
}
return info;
}
}
public abstract class AuthorizingRealm extends AuthenticatingRealm
implements Authorizer, Initializable, PermissionResolverAware, RolePermissionResolverAware {
protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {
//省略……
//从缓存中获取权限信息
Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache();
if (cache != null) {
Object key = getAuthorizationCacheKey(principals);
info = cache.get(key);
}
//如果从缓存中获取不到,调用我们实现的 doGetAuthorizationInfo 获取,然后放入缓存。
if (info == null) {
info = doGetAuthorizationInfo(principals);
if (info != null && cache != null) {
Object key = getAuthorizationCacheKey(principals);
cache.put(key, info);
}
}
return info;
}
SessionManager的缓存:
public class DefaultSessionManager extends AbstractValidatingSessionManager implements CacheManagerAware {
@Override
public void setCacheManager(CacheManager cacheManager) {
this.cacheManager = cacheManager;
//将sessionDAO 的缓存策略更新为cacheManager
applyCacheManagerToSessionDAO();
}
//将sessionDAO 的缓存策略更新为cacheManager
private void applyCacheManagerToSessionDAO() {
if (this.cacheManager != null && this.sessionDAO != null && this.sessionDAO instanceof CacheManagerAware) {
((CacheManagerAware) this.sessionDAO).setCacheManager(this.cacheManager);
}
}
}
public abstract class CachingSessionDAO extends AbstractSessionDAO implements CacheManagerAware {
//尝试从缓存中读取Session,读取不到调用父类的实现
@Override
public Session readSession(Serializable sessionId) throws UnknownSessionException {
Session s = getCachedSession(sessionId);
if (s == null) {
s = super.readSession(sessionId);
}
return s;
}
//更新Session信息,并重新缓存Session
@Override
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());
}
}
//首先从缓存中删除,然后从EIS中删除
@Override
public void delete(Session session) {
uncache(session);
doDelete(session);
}
//调用 父类的实现,并缓存起来
@Override
public Serializable create(Session session) {
Serializable sessionId = super.create(session);
cache(session, sessionId);
return sessionId;
}
//将Session放入缓存
protected void cache(Session session, Serializable sessionId) {
if (session == null || sessionId == null) {
return;
}
Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
if (cache == null) {
return;
}
cache(session, sessionId, cache);
}
//从缓存中删除Session
protected void uncache(Session session) {
if (session == null) {
return;
}
Serializable id = session.getId();
if (id == null) {
return;
}
Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
if (cache != null) {
cache.remove(id);
}
}
}