吃透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));
    }
}

posted @ 2022-07-17 12:14  小大宇  阅读(78)  评论(0编辑  收藏  举报