Cache Plugin 实现过程

原创
2013/11/07 20:56
阅读数 6.2K

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

前几天写过一篇文章,关于如何实现 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 的整体框架中去。

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

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

public abstract class PluginAspect implements Proxy {

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

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

第二步:改造 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 开始 》。

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

展开阅读全文
加载中
点击加入讨论🔥(4) 发布并加入讨论🔥
打赏
4 评论
26 收藏
1
分享
返回顶部
顶部