前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >jetcache源码分析之Cached注解

jetcache源码分析之Cached注解

作者头像
johnhuster的分享
发布2024-02-25 09:40:03
1350
发布2024-02-25 09:40:03
举报
文章被收录于专栏:johnhusterjohnhuster

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();
        }
    }
本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-02-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com