jetcache源码分析之Cached注解

2024-02-25 09:40:03 浏览数 (2)

jetcache是阿里旗下的一款缓存框架,详情不在这里赘述,下面直入主题,聊聊jetcache的@Cached注解的工作原理,@Cached注解底层是通过动态代理实现的,那么具体@Cached背后的工作原理是什么呢,下面进入细节

首先看下JetCacheInterceptor这个类,正如其名所示,这个一个拦截器,实现了MethodInterceptor接口,下面看下这个类的invoke方法

代码语言:javascript复制
    public Object invoke(final MethodInvocation invocation) throws Throwable {
        if (configProvider == null) {
            configProvider = applicationContext.getBean(ConfigProvider.class);
        }
        if (configProvider != null && globalCacheConfig == null) {
            globalCacheConfig = configProvider.getGlobalCacheConfig();
        }
        if (globalCacheConfig == null || !globalCacheConfig.isEnableMethodCache()) {
            return invocation.proceed();
        }
        if (cacheManager == null) {
            cacheManager = applicationContext.getBean(CacheManager.class);
            if (cacheManager == null) {
                logger.error("There is no cache manager instance in spring context");
                return invocation.proceed();
            }
        }

        Method method = invocation.getMethod();
        Object obj = invocation.getThis();
        CacheInvokeConfig cac = null;
        if (obj != null) {
            String key = CachePointcut.getKey(method, obj.getClass());
            cac  = cacheConfigMap.getByMethodInfo(key);
        }

        // 方法上没有使用@Cached注解时直接调用初始方法(比如直接读数据库)获取数据

        if (cac == null || cac == CacheInvokeConfig.getNoCacheInvokeConfigInstance()) {
            return invocation.proceed();
        }

        // 创建CacheInvokeContext,把方法调用的上下文(传参、方法、对象等)放入CacheInvokeContext对象,然后调用CacheHandler的invoke方法实现真正的功能

        CacheInvokeContext context = configProvider.newContext(cacheManager).createCacheInvokeContext(cacheConfigMap);
        context.setTargetObject(invocation.getThis());
        context.setInvoker(invocation::proceed);
        context.setMethod(method);
        context.setArgs(invocation.getArguments());
        context.setCacheInvokeConfig(cac);
        context.setHiddenPackages(globalCacheConfig.getHiddenPackages());
        return CacheHandler.invoke(context);
    }

JetCacheInterceptor的invoke方法并没有真正涉及@Cached注解的工作原理,真正的实现在CacheHandler的invoke方法实现,下面看下CacheHandler的invoke方法

代码语言:javascript复制
    public static Object invoke(CacheInvokeContext context) throws Throwable {
        if (context.getCacheInvokeConfig().isEnableCacheContext()) {
            try {
                CacheContextSupport._enable();
                //
                return doInvoke(context);
            } finally {
                CacheContextSupport._disable();
            }
        } else {
            return doInvoke(context);
        }
    }


    private static Object doInvoke(CacheInvokeContext context) throws Throwable {
        CacheInvokeConfig cic = context.getCacheInvokeConfig();
        CachedAnnoConfig cachedConfig = cic.getCachedAnnoConfig();
        if (cachedConfig != null && (cachedConfig.isEnabled() || CacheContextSupport._isEnabled())) {
            // 使用了@Cached注解,并且开启缓存,所以看下invokeWithCached这个方法
            return invokeWithCached(context);
        } else if (cic.getInvalidateAnnoConfigs() != null || cic.getUpdateAnnoConfig() != null) {
            // 使用了@CacheUpdate、@CacheInvalidate注解
            return invokeWithInvalidateOrUpdate(context);
        } else {
            // 没使用上述三个注解
            return invokeOrigin(context);
        }
    }

上面的代码追溯到了CacheHandler的invokeWithCached方法,这个方法是@Cached注解最终功能试下,所以主要看invokeWithCached实现就可以

代码语言:javascript复制
    private static Object invokeWithCached(CacheInvokeContext context)
            throws Throwable {
        // 获取@Cached注解配置参数
        CacheInvokeConfig cic = context.getCacheInvokeConfig();
        CachedAnnoConfig cac = cic.getCachedAnnoConfig();
        // 获取@Cached注解使用的缓存
        Cache cache = context.getCacheFunction().apply(context, cac);
        if (cache == null) {
            logger.error("no cache with name: "   context.getMethod());
            return invokeOrigin(context);
        }

        // Spel表达式解析key

        Object key = ExpressionUtil.evalKey(context, cic.getCachedAnnoConfig());
        if (key == null) {
            // 没有配置key,全局性数据
            return loadAndCount(context, cache, null);
        }

        // 执行@Cached中condition条件表达式,判断是否要缓存数据
        if (!ExpressionUtil.evalCondition(context, cic.getCachedAnnoConfig())) {
            return loadAndCount(context, cache, key);
        }

        try {
            // 这里创建了一个CacheLoader的类实例,该类用于当缓存不存在对应key数据时调用原始方法(读数据库或者调用api请求数据)获取数据
            CacheLoader<?,?> loader = new CacheLoader() {
                @Override
                public Object load(Object k) throws Throwable {
                    // 原始方法调用
                    Object result = invokeOrigin(context);
                    context.setResult(result);
                    return result;
                }

                @Override
                public boolean vetoCacheUpdate() {
                    // 判断@Cached后置condition条件是否有效,比如如果调用api请求失败,这种情况是无需缓存数据的
                    return !ExpressionUtil.evalPostCondition(context, cic.getCachedAnnoConfig());
                }
            };
            // 判断缓存值是否存在,不存在则调用原始方法获取数据,这里面涉及到了缓存击穿的防护
            Object result = cache.computeIfAbsent(key, loader);
            return result;
        } catch (CacheInvokeException e) {
            throw e.getCause();
        }
    }

0 人点赞