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();
}
}