Cache Plugin 实现过程

本文是《轻量级 Java Web 框架架构设计》的系列博文。 java

前几天写过一篇文章,关于如何实现 Cache Plugin,错过的朋友能够看这篇:《可否让 Cache 变得更加优雅?》。 数据库

今天,我想和你们分享一下这个 Cache Plugin 的实现过程。再次感谢 Bieber 的解决方案! 缓存

我以为 Cache 应该是一种带有 AOP 性质的 Plugin,相似地,还会有 Log Plugin、Permission Plugin 等。不妨先对这些 AOP Plugin 作一个通用的解决方案,或许之后实现其余同类型的 Plugin 会更加轻松。 架构

咱们知道 AOP 内部其实使用了 Proxy,在 Smart 中已经对 AOP 作了一个改进,如今是具备 Proxy Chain 风格的 AOP 了,也就是说,AOP 就像 Chain 同样能够进行链式调用。对于这个思路还不太清楚的朋友,能够参考这篇:《使用“链式代理”实现 AOP》。再次感谢 黎明伟 提供的解决方案! 框架

因而可知,首先须要对 Proxy 作一个扩展,好比增长一个 PluginAspect(与之前的 BaseAspect 同等级别),而后在 Cache Plugin 中去扩展这个 PluginAspect。此外,还须要对 AOP Aspect 的加载部位作一个手术,给它开一个口子,让 PluginAspect 能够自由地插入到 Smart AOP 的总体框架中去。 ide

好了,我想大体的意思已经表达清楚了,下面即是具体的实现过程。 优化

第一步:在 Smart Framework 中定义一个 PluginAspect。 spa

public abstract class PluginAspect implements Proxy {

    public abstract List<Class<?>> getTargetClassList();
}

可见这个 PluginAspect 这个抽象类实现了 Proxy 接口(它和 BaseAspect 同样),并提供一个抽象方法 getTargetClassList,这个方法是让该抽象类的子类(让 Cache Plugin 来实现)来提供有关须要拦截的目标类。由于实现了 Proxy 接口,因此子类还要实现 doProxy 方法(还记得它长什么样子吗?)。 .net

第二步:改造 AOPHelper,让它加载 PluginAspect。 插件

如今轮到 AOPHelper 了,有必要对 Aspect 加载的代码进行一个优化,如今的 Aspect 分为三类:

  1. User Aspect(用户切面,由用户自定义的切面
  2. System Aspect(系统切面,有系统提供的切面,例如:事务切面)
  3. Plugin Aspect(插件切面,例如:缓存切面)

须要对初始化 Aspect 的地方进行修改,代码片断以下:

public class AOPHelper {

    ...

    private Map<Class<?>, List<Class<?>>> createAspectMap() throws Exception {
        // 定义 Aspect Map
        Map<Class<?>, List<Class<?>>> aspectMap = new LinkedHashMap<Class<?>, List<Class<?>>>();
        // 添加插件切面
        addPluginAspect(aspectMap);
        // 添加用户切面
        addUserAspect(aspectMap);
        // 添加事务切面
        addTransactionAspect(aspectMap);
        // 返回 Aspect Map
        return aspectMap;
    }

    private void addPluginAspect(Map<Class<?>, List<Class<?>>> aspectMap) throws Exception {
        // 获取插件包名下父类为 PluginAspect 的全部类(插件切面类)
        List<Class<?>> pluginAspectClassList = ClassUtil.getClassListBySuper("com.smart.plugin", PluginAspect.class);
        if (CollectionUtil.isNotEmpty(pluginAspectClassList)) {
            // 遍历全部插件切面类
            for (Class<?> pluginAspectClass : pluginAspectClassList) {
                // 建立插件切面类实例
                PluginAspect pluginAspect = (PluginAspect) pluginAspectClass.newInstance();
                // 将插件切面类及其所对应的目标类列表放入 Aspect Map 中
                aspectMap.put(pluginAspectClass, pluginAspect.getTargetClassList());
            }
        }
    }

    private void addUserAspect(Map<Class<?>, List<Class<?>>> aspectMap) throws Exception {
        // 获取切面类
        List<Class<?>> aspectClassList = ClassHelper.getInstance().getClassListBySuper(BaseAspect.class);
        // 排序切面类
        sortAspectClassList(aspectClassList);
        // 遍历切面类
        for (Class<?> aspectClass : aspectClassList) {
            // 判断 @Aspect 注解是否存在
            if (aspectClass.isAnnotationPresent(Aspect.class)) {
                // 获取 @Aspect 注解
                Aspect aspect = aspectClass.getAnnotation(Aspect.class);
                // 建立目标类列表
                List<Class<?>> targetClassList = createTargetClassList(aspect);
                // 初始化 Aspect Map
                aspectMap.put(aspectClass, targetClassList);
            }
        }
    }

    private void addTransactionAspect(Map<Class<?>, List<Class<?>>> aspectMap) {
        // 使用 TransactionAspect 横切全部 Service 类
        List<Class<?>> serviceClassList = ClassHelper.getInstance().getClassListBySuper(BaseService.class);
        aspectMap.put(TransactionAspect.class, serviceClassList);
    }

    ...
}

注意以上的 createAspectMap 方法,在其中依次建立:插件切面、用户切面、事务切面,这是为了让事务控制在 Proxy Chain 的末端,从而才能尽可能靠近目标方法,因此将其加载顺序放在了最后。相关逻辑请参见代码中的注释,您若是有疑问,能够给我留言。

如今框架性质的代码终于完成了,剩下的就是插件的相关代码了。首先要作的就是定义一个 CacheAspect,让它去扩展 PluginAspect。

第三步:在 Cache Plugin 中定义一个 CacheAspect。

代码量不算太多,直接贴出来吧:

public class CacheAspect extends PluginAspect {

    @Override
    public List<Class<?>> getTargetClassList() {
        // 设置目标类列表(获取带有 @Cachable 注解的目标类)
        return ClassHelper.getInstance().getClassListByAnnotation(Cachable.class);
    }

    @Override
    @SuppressWarnings("unchecked")
    public Object doProxy(ProxyChain proxyChain) throws Exception {
        // 定义方法返回值
        Object result = null;
        // 获取目标方法
        Class<?> cls = proxyChain.getTargetClass();
        Method method = proxyChain.getTargetMethod();
        Object[] params = proxyChain.getMethodParams();
        // 判断不一样类型的 Cache 注解
        if (method.isAnnotationPresent(CachePut.class)) {
            // 若为 @CachePut 注解,则首先从 Cache 中获取
            // 若 Cache 中不存在,则从 DB 中获取,最后放入 Cache 中
            Cache cache = getCache(cls, method);
            if (cache != null) {
                String cacheKey = getCacheKey(method, params);
                result = cache.get(cacheKey); // 从 Cache 中获取
                if (result == null) {
                    result = proxyChain.doProxyChain(); // 从 DB 中获取
                    if (result != null) {
                        cache.put(cacheKey, result); // 放入 Cache 中
                    }
                }
            }
        } else if (method.isAnnotationPresent(CacheClear.class)) {
            // 若为 @CacheRemove 注解,则首先进行数据库操做,而后刷新缓存
            result = proxyChain.doProxyChain();
            clearCache(cls, method);
        } else {
            // 若不带有任何的 Cache 注解,则直接进行数据库操做
            result = proxyChain.doProxyChain();
        }
        // 返回结果
        return result;
    }

    private <K, V> Cache<K, V> getCache(Class<?> cls, Method method) {
        // 从 @CachePut 注解中获取 Cache Name,并经过 CacheManager 获取所对应的 Cache
        CachePut cachePut = method.getAnnotation(CachePut.class);
        String cacheName = cachePut.value();
        return CacheFactory.createCache(cls, cacheName);
    }

    private String getCacheKey(Method method, Object[] params) {
        // Cache Key = 方法名-参数
        return method.getName() + "-" + Arrays.toString(params);
    }

    private void clearCache(Class<?> cls, Method method) {
        // 从 @CacheClear 注解中获取相应的 Cache Name(一个或多个),并经过 CacheManager 销毁所对应的 Cache
        CacheClear cacheClear = method.getAnnotation(CacheClear.class);
        String[] cacheNames = cacheClear.value();
        if (ArrayUtil.isNotEmpty(cacheNames)) {
            CacheManager cacheManager = CacheFactory.getCacheManager(cls);
            for (String cacheName : cacheNames) {
                cacheManager.destroyCache(cacheName);
            }
        }
    }
}

最核心的 Cache 逻辑放在了 doProxy 方法中,这个逻辑是 BaseAspect 没法实现的,因此要单独作一个 PluginAspect 出来,并让它实现 Proxy 接口,这样它的子类(也就是 CacheAspect)就有能力彻底控制 Proxy 的操做行为了。相信代码中的注释,足够让您明白这是在作什么,固然您的疑问对于我而言十分期待。

还要作什么呢?不用作了。

难道不用在应用程序里添加一点东西吗?固然要,不然又怎么加载 Cache Plugin 呢!

第四步:添加 Cache Plugin 依赖。

在 Maven 的 pom.xml 中添加 Cache Plugin 的依赖关系,以下:

...
    <dependencies>

        ...

        <dependency>
            <groupId>com.smart</groupId>
            <artifactId>smart-plugin-cache</artifactId>
            <version>1.0</version>
        </dependency>

        ...

    </dependencies>
...

至此,Cache Plugin 与 Smart Framework 无缝集成,在应用程序里无需作任何事情,只需配置 Maven 依赖便可使用 Cache Plugin。

固然,以上基本实现了 Cache 注解的功能,但未必在实际场景中都会使用这种注解方式,毕竟控制粒度是方法级的(粒度较粗)。若是要控制细粒度级别的 Cache,建议直接使用 Smart Cache API,也就是说可经过这种方法来使用,请参见《Smart Plugin —— 从一个简单的 Cache 开始 》。

您是否对以上实现持怀疑或否认态度呢?很是期待您的建议!相互讨论,这样才能共同进步。

相关文章
相关标签/搜索