吃透Shiro源码4----Cache、CacheManager
技术手法
(1)究竟什么是缓存
缓存这个词语,我耳朵都快听出茧子了,什么Redis、Ecache。不过,到底什么是缓存,说实在的,我一直很模糊其概念。今天终于接近了缓存代码的源头。缓存到底是什么?我的总结:缓存对象是一个可以封装多组键值对的特殊对象。因此,缓存可以视为一种容器。
如下代码展示了缓存最顶层的接口。原来缓存一点都不神秘,它就是一种可以用来增删对象的容器。
public interface Cache<K, V> ...
/**
* 存
*
* @param key key
* @param value value
*/
void put(K key, V value) throws CacheException;
/**
* 取
*
* @param key key
* @return value
*/
V get(K key) throws CacheException;
/**
* 删
*
* @param k key
* @return value
*/
V remove(K k) throws CacheException;
(2)用Map作为缓存实现
既然缓存是容器,那么在单机部署或者是单线程情况下,可以创建一个简单的缓存实现。用HashMap等Map接口实现作为底层实现。如果为了保证线程安全,可以使用ConcurrentMap接口实现。下面的代码中实现了一个简易的缓存实现类。
public class MapCache<K, V> implements Cache<K, V> ...
/**
* 底层实现Map
*/
private final Map<K, V> map;
/**
* 此缓存的名字
*/
private final String name;
public MapCache(String name, Map<K, V> backingMap) {
if (name == null) {
throw
new IllegalArgumentException("缓存对象name为空");
}
if (backingMap == null) {
throw
new IllegalArgumentException("缓存对象backingMap为空");
}
this.map = backingMap;
this.name = name;
}
@Override
public void put(K key, V value) throws CacheException {
//HashMap是可以允许键为空的,放在其内部数组的第一个角标上
map.put(key, value);
}
@Override
public V get(K key) throws CacheException {
return map.get(key);
}
@Override
public void clear() throws CacheException {
map.clear();
}
(3)如何管理缓存生命周期
Shiro使用CacheManager接口来管理Cache对象的生命周期。
public interface CacheManager {
/**
* 通过缓存的名字获取缓存实例对象
* 如果没有此缓存实例对象,
* 那么就按照此名字创建一个缓存实例
*
* @param name 缓存实例名称
* @return 指明名称的缓存对象
* @throws CacheException 异常
*/
<K, V> Cache<K, V> getCache(String name)
throws CacheException;
}
/**
* 一个很简单的缓存管理器
*/
public abstract class AbstractCacheManager
implements CacheManager ...
//缓存的name对应缓存Cache对象
private final ConcurrentMap<String, Cache> caches;
public AbstractCacheManager() {
this.caches = new ConcurrentHashMap<>();
}
@Override
public <K, V> Cache<K, V> getCache(String name) {
...
Cache cache = caches.get(name);
if (cache == null) {
cache = createCache(name);
//把新创建的cache存到内部Map中。
Cache existing = caches.putIfAbsent(name, cache);
if (existing != null) {
cache = existing;
}
}
//未检查
return cache;
}
/**
* 通过提供的名字创建缓存对象
*
* @param name 期望的缓存实例名称
* @return 新创建的缓存实例
*/
protected abstract <K, V> Cache<K, V>
createCache(String name);
}
(4)AOP解析方法上的注解思路
学了不少Shiro源码,我发现大佬们的代码风格总是面向接口。比如如何解析出方法上的注解。大佬先创建了AnnotationHandler用来封装要被处理的注解类。AnnotationResolver用来从具体的方法上解析出用了指定注解类的注解对象。
具体的实现类还没有研究到,不过总体的思路已经掌握。
/**
* 此类用于处理注解,提供一个基础的抽象类
*/
public abstract class AnnotationHandler ...
/**
* 要被处理的注解
*/
private Class<? extends Annotation> annotationClass;
}
public interface AnnotationResolver ...
/**
* 返回方法执行情况对象MethodInvocation上指定类型的注解实例
* 如果没有使用注解,则返回null
*
* @param mi 方法执行调用对象的情况
* @param clazz 指定注解
* @return 指定注解对象
*/
Annotation getAnnotation(MethodInvocation mi,
Class<? extends Annotation> clazz);
}
/**
* 本类用于解析出方法上的具体的注解实例对象
* AnnotationHandler它提供了要被处理的注解类
* AnnotationResolver它提供了从方法上获取指定的注解类对象实例
*/
public abstract class AnnotationMethodInterceptor ...
private AnnotationHandler handler;
/**
* 用于超找方法上的注解的解析器
*/
private AnnotationResolver resolver;
/**
* 要被处理的方法上的注解对象
*
* @param mi 方法执行具体情况对象
* @return 方法上正在被处理的注解对象
*/
protected Annotation getAnnotation(MethodInvocation mi) {
return this.resolver.
getAnnotation(mi, this.handler.getAnnotationClass());
}
}
重点研究类
import java.lang.reflect.Method;
/**
* 方法正在调用的情况
*/
public interface MethodInvocation {
/**
* 继续方法调用链,如果是最后一个方法,则调用自己
*
* @return 方法执行结果
* @throws Throwable 异常
*/
Object proceed() throws Throwable;
/**
* 返回要调用的实际方法{@link Method}
*
* @return 实际要被调用的方法
*/
Method getMethod();
/**
* 获取方法上的参数,没有返回null
*
* @return 方法参数
*/
Object[] getArguments();
/**
* 返回保存当前连接点的对象
* 返回被代理的对象?
*
* @return 某对象,看具体怎么实现的吧
*/
Object getThis();
}
public interface MethodInterceptor {
/**
* 调用指定的{@link MethodInvocation#proceed()},
* 从而允许实现围绕实际调用进行前/后/最终执行。
*
* @param methodInvocation methodInvocation
* @return 方法返回值
* @throws Throwable 异常
*/
Object invoke(MethodInvocation methodInvocation)
throws Throwable;
}
import java.lang.annotation.Annotation;
public interface AnnotationResolver {
/**
* 返回方法执行情况对象{@link MethodInvocation}上
* 指定类型的注解实例
* 如果没有使用注解,则返回null
*
* @param mi方法执行调用对象
* @param clazz 指定注解
* @return 指定注解对象
*/
Annotation getAnnotation(MethodInvocation mi,
Class<? extends Annotation> clazz);
}
package com.wise.security.aop;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
/**
* 返回方法上指定类型的注解对象
*/
public class DefaultAnnotationResolver
implements AnnotationResolver {
@Override
public Annotation getAnnotation(MethodInvocation methodInvocation,
Class<? extends Annotation> clazz) {
Method method = methodInvocation.getMethod();
if (method == null) {
throw new IllegalArgumentException("调用的方法是空");
}
//获取方法上指定类型的注解实例
Annotation annotation = method.getAnnotation(clazz);
//如果是空,再尝试一下去代理的对象上找找看?
if(annotation == null){
annotation = methodInvocation.getThis().getClass().getAnnotation(clazz);
}
return annotation;
}
}
import java.lang.annotation.Annotation;
/**
* 本类用于解析出方法上的具体的注解实例对象
* AnnotationHandler它提供了要被处理的注解类
* AnnotationResolver它提供了从方法上获取指定的注解类对象实例
*/
public abstract class AnnotationMethodInterceptor extends MethodInterceptorSupport {
private AnnotationHandler handler;
/**
* 用于超找方法上的注解的解析器
*/
private AnnotationResolver resolver;
public AnnotationMethodInterceptor(AnnotationHandler handler) {
this(handler, new DefaultAnnotationResolver());
}
public AnnotationMethodInterceptor(AnnotationHandler handler, AnnotationResolver resolver) {
if (handler == null) {
throw new IllegalArgumentException("注解处理器为空");
}
if (resolver == null) {
resolver = new DefaultAnnotationResolver();
}
this.handler = handler;
this.resolver = resolver;
}
public AnnotationHandler getHandler() {
return handler;
}
public void setHandler(AnnotationHandler handler) {
this.handler = handler;
}
public AnnotationResolver getResolver() {
return resolver;
}
public void setResolver(AnnotationResolver resolver) {
this.resolver = resolver;
}
/**
* 是否
* 能够处理方法上的注解
* 的拦截器
*
* @param mi
* @return
*/
public boolean supports(MethodInvocation mi) {
return getAnnotation(mi) != null;
}
/**
* 要被处理的方法上的注解对象
*
* @param mi 方法执行具体情况对象
* @return 方法上正在被处理的注解对象
*/
protected Annotation getAnnotation(MethodInvocation mi) {
return this.resolver.getAnnotation(mi,
this.handler.getAnnotationClass());
}
}
import java.util.Collection;
import java.util.Set;
/**
* 缓存有效地存储临时对象,主要是为了提高应用程序的性能。
* Shiro本身并没有实现完整的Cache机制,因为那超出了安全框架的核心能力
* 相反,此接口在基础层的顶部提供了一个抽象(包装器)API。
* 缓存框架的缓存实例(例如JCache,Ehcache,JCS,OSCache,JBossCache,TerraCotta,Coherence,
* GigaSpaces等),允许Shiro用户配置他们选择的任何缓存机制。
*/
public interface Cache<K, V> {
/**
* 存入
*
* @param key key
* @param value value
*/
void put(K key, V value) throws CacheException;
/**
* 取
*
* @param key key
* @return value
*/
V get(K key) throws CacheException;
/**
* 清除所有
*/
void clear() throws CacheException;
/**
* 删除
*
* @param k key
* @return value
*/
V remove(K k) throws CacheException;
/**
* 获取缓存内K-V个数
*
* @return K-V个数
*/
int size();
/**
* 返回所有key的视图
*
* @return key视图
*/
Set<K> keys();
/**
* 返回所有value视图
*
* @return
*/
Collection<V> values();
}
import com.wise.security.util.CollectionUtils;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
/**
* 缓存对象到底是什么?
* 缓存对象其实就是一组可以封装多个键值对的特殊对象
* 缓存因此可以视为容器
*/
public class MapCache<K, V> implements Cache<K, V> {
/**
* 内部的Map,适用于单线程
*/
private final Map<K, V> map;
/**
* 此缓存的名字
*/
private final String name;
public MapCache(String name, Map<K, V> backingMap) {
if (name == null) {
throw new IllegalArgumentException("缓存对象name为空");
}
if (backingMap == null) {
throw new IllegalArgumentException("缓存对象backingMap为空");
}
this.map = backingMap;
this.name = name;
}
@Override
public void put(K key, V value) throws CacheException {
//HashMap是可以运行键为空的,放在其内部数组的第一个角标上
map.put(key, value);
}
@Override
public V get(K key) throws CacheException {
return map.get(key);
}
@Override
public void clear() throws CacheException {
map.clear();
}
@Override
public V remove(K k) throws CacheException {
return map.remove(k);
}
@Override
public int size() {
return map.size();
}
@Override
public Set<K> keys() {
Set<K> keys = map.keySet();
if (CollectionUtils.isEmpty(keys)) {
return Collections.emptySet();
}
return Collections.unmodifiableSet(keys);
}
@Override
public Collection<V> values() {
Collection<V> values = map.values();
if (CollectionUtils.isEmpty(values)) {
return Collections.emptyList();
}
return Collections.unmodifiableCollection(values);
}
}
import com.wise.security.util.Destroyable;
import com.wise.security.util.LifecycleUtils;
import com.wise.security.util.StringUtils;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* 一个很简单的缓存管理器
*/
public abstract class AbstractCacheManager implements CacheManager, Destroyable {
private final ConcurrentMap<String, Cache> caches;
public AbstractCacheManager() {
this.caches = new ConcurrentHashMap<>();
}
@Override
public <K, V> Cache<K, V> getCache(String name) throws CacheException {
if (!StringUtils.hasText(name)) {
throw new IllegalArgumentException("缓存对象名称为空");
}
Cache cache = caches.get(name);
if (cache == null) {
cache = createCache(name);
//把新创建的cache存到内部Map中。此方法就是value为空,就存K-V
Cache existing = caches.putIfAbsent(name, cache);
if (existing != null) {
cache = existing;
}
}
//未检查
return cache;
}
@Override
public void destroy() throws Exception {
for (Cache cache : caches.values()) {
LifecycleUtils.destroy(cache);
}
caches.clear();
}
/**
* 通过提供的名字创建缓存管理器
*
* @param name 期望的缓存实例名称
* @return 新创建的缓存实例
*/
protected abstract <K, V> Cache<K, V> createCache(String name);
}
import java.util.concurrent.ConcurrentHashMap;
/**
* 有内存限制的缓存管理器
*/
public class MemoryConstrainedCacheManager
extends AbstractCacheManager {
/**
* Shiro源码中提供了一个在单线程下线程安全的Map接口实现
* 并且能够帮助限制内存,因此这个类才被命名为:有内存限制的缓存管理器
* <p>
* SoftHashMap源代码我就不深究了
* 为了模拟出类似的效果
* 我为缓存接口内部实现创建一个ConcurrentHashMap吧
*
* @param name 期望的缓存实例名称
* @return 缓存对象
*/
@Override
protected <K, V> Cache<K, V> createCache(String name) {
return new MapCache<>
(name, new ConcurrentHashMap<>(16));
}
}