1. springboot cache 的使用
a.pom引入jar spring-boot-starter-cache
b.启动类增加注解@EnableCaching
c.需要缓存的方法增加注解 @Cacheable(cacheNames = "com:xxx",key = "''+#id")
图1
2. Cacheable 的实现原理猜测
实现原理是什么呢?脑海第一反应应该当然是大名鼎鼎是AOP、动态代理、Interceptor这些概念,那我们怎么去验证呢?
3. Cacheable 的实现原理验证
从调用该方法getById的入口打断点跟进去到了CglibAopProxy动态代理,看到了关键的代码获取Interceptor调用链
# DynamicAdvisedInterceptor.intercept 方法
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
具体的Interceptor 如下图,果然有个一个和Cache相关的CacheInterceptor自此验证我们在第二部的猜测
图2
4. 原理是怎么实现的呢?
a.继续第3步的源码往里面走看看Interceptor是从哪里获取到的,往下面走看到是通过advisor来获取缓存的Interceptor,如下图3
b.继续往下走定位到了具体的类 BeanFactoryCacheOperationSourceAdvisor,如下图4
c.后面的逻辑就是从advisors中获取配置的Interceptor,看到了BeanFactory相关我们一定要想到是应用启动的时候初始化的相关操作,于是我们应该想到第1步的启动类增加@EnableCaching这个注解,所以我们回到开始的位置往下跟踪。
d.我们发现@EnableCacheing 下面有一个 @Import({CachingConfigurationSelector.class}) 注解,我们跟进去到selectImports —>getProxyImports,在getProxyImports方法里面看到了下面的代码 图5
e.继续进入到ProxyCachingConfiguration 类,终于看到了BeanFactoryCacheOperationSourceAdvisor 是不是就和第4步关联起来了?是不是就和CacheInterceptor关联起来了?图6
5.具体方法的@Cacheable 注解是怎么和CacheInterceptor 关联起来的呢?
我们继续从图4往下走
# 第一步
定位到了 CacheOperationSourcePointcut.matches()方法
#第二步
AbstractFallbackCacheOperationSource.getCacheOperations()方法
#第三步 最终key 从 attributeCache 获取
public Collection<CacheOperation> getCacheOperations(Method method, @Nullable Class<?> targetClass) {
....省略非关键代码
# 从缓存中获取key
Object cacheKey = this.getCacheKey(method, targetClass);
Collection<CacheOperation> cached = (Collection)this.attributeCache.get(cacheKey);
if (cached != null) {
return cached != NULL_CACHING_ATTRIBUTE ? cached : null;
} else {
# 如果缓存中没有key ,判断该方法是否存在CacheOperation操作
Collection<CacheOperation> cacheOps = this.computeCacheOperations(method, targetClass);
if (cacheOps != null) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Adding cacheable method '" + method.getName() + "' with attribute: " + cacheOps);
}
# 存在CacheOperation操作,放入缓存
this.attributeCache.put(cacheKey, cacheOps);
} else {
# 存在CacheOperation操作,存入空集合
this.attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE);
}
return cacheOps;
}
}
跟踪 this.computeCacheOperations()方法,定位到parseCacheAnnotations()方法,最终看到了Cacheable、CacheEvict、CachePut等相关注解,自此所有谜底就已经全部解开了。
# SpringCacheAnnotationParser.parseCacheAnnotations()方法
Collection<CacheOperation> ops = new ArrayList(1);
anns.stream().filter((ann) -> {
return ann instanceof Cacheable;
}).forEach((ann) -> {
ops.add(this.parseCacheableAnnotation(ae, cachingConfig, (Cacheable)ann));
});
anns.stream().filter((ann) -> {
return ann instanceof CacheEvict;
}).forEach((ann) -> {
ops.add(this.parseEvictAnnotation(ae, cachingConfig, (CacheEvict)ann));
});
anns.stream().filter((ann) -> {
return ann instanceof CachePut;
}).forEach((ann) -> {
ops.add(this.parsePutAnnotation(ae, cachingConfig, (CachePut)ann));
});
anns.stream().filter((ann) -> {
return ann instanceof Caching;
}).forEach((ann) -> {
this.parseCachingAnnotation(ae, cachingConfig, (Caching)ann, ops);
});
return ops;
6. 总结
前面我们自下而上的从假设到验证的梳理了springboot下@Cachebale的实现原理,我们再自上而下的总结下大的步骤。
a.EnableCaching 初始化advisor,注册interceptor拦截器
b.容器初始化的时候把带有Cache相关注解的方法放入attributeCache中缓存起来
c.带有Cache的方法通过Cglib动态代理增强织入拦截器
d.结合a、b的初始化信息定位到CacheIntercepthor拦截器,实现缓存的增、删、改功能