@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
boolean proxyTargetClass() default false; //动态代理模式中,Spring AOP对于具体类的代理是使用JavaProxy还是cglib
AdviceMode mode() default AdviceMode.PROXY; //Spring AOP使用动态代理还是原生ASPECTJ来实现
int order() default Ordered.LOWEST_PRECEDENCE; //启动顺序
}
打上@EnableCaching就能启动注释风格的SpringCache功能的具体实现机制在上一篇中专门做了介绍,这里不再赘述。public @interface CachePut {
String[] value(); //缓存的名字,可以把数据写到多个缓存
String key() default ""; //缓存key,如果不指定将使用默认的KeyGenerator生成
String condition() default ""; //满足缓存条件的数据才会放入缓存,CachePut的condition只在调用方法之后判断,因此可以得到result
String unless() default ""; //用于否决缓存更新的,不像condition,该表达只在方法执行之后判断,此时可以拿到返回值result进行判断了
}
使用示例如下:
@CachePut(value = "mycache", key = "#user.id", condition = "#user.id ne 12")
public User save(User user) {
return user;
}
上述配置的含义是:在调用该方法时,会把符合条件的(user.id!=12的)user.id作为key,返回值作为value放入缓存;系统中可能存在多个cache,将返回结果保存在名称为"mycache"的缓存中。public @interface CacheEvict {
String[] value(); //缓存的名字,可以从多个缓存中移除数据
String key() default ""; //缓存key,如果不指定将使用默认的KeyGenerator生成
String condition() default ""; //满足缓存条件的数据才会从缓存中移除,condition在调用方法之前和之后都会判断
boolean allEntries() default false; //是否移除所有数据
boolean beforeInvocation() default false;//是调用方法之前移除/还是调用之后移除
}
使用示例如下:
@CacheEvict(value = "mycache", key = "#user.id")
public void delete(User user) {
users.remove(user);
}
上述配置的含义是:从名为mycache的缓存中移除key值为user.id的数据。
@CacheEvict(value={"mycache", "mycache2"}, allEntries = true)
public void clearCache(){
}
上述配置的含义是:清除mycache和mycache2中的所有缓存。public @interface Cacheable {
String[] value(); //缓存的名字,可以从多个缓存中读取数据
String key() default ""; //缓存key,如果不指定将使用默认的KeyGenerator生成
String condition() default "";//满足缓存条件的数据才会从缓存中读取,或者在不存在的时候添加到缓存中
String unless() default ""; //用于否决缓存更新的,该表达只在方法执行之后判断,此时可以拿到返回值result进行判断了
}
使用实例如下:
@Cacheable(value="mycache2", key = "#username.concat(#email)", condition = "#username eq 'wangd'")
public User findByUsernameAndEmail(String username, String email){
Random random = new Random();
logger.info("cache2 miss, invoke find by name and email, name:" + username +
", email:"+email);
User user = new User(System.currentTimeMillis()+random.nextInt(10000),
username,
email);
return user;
}
上述配置表示:在调用该方法时,当符合条件(username的值为'wangd')时,会使用username+email作为key,查询缓存,如果缓存中存在该key则直接将其对应的value返回,否则将方法调用的返回值作为value放入缓存。public @interface Caching {
Cacheable[] cacheable() default {}; //声明多个@Cacheable
CachePut[] put() default {}; //声明多个@CachePut
CacheEvict[] evict() default {}; //声明多个@CacheEvict
}
使用示例如下:
@Caching(
put = {
@CachePut(value = "mycache", key = "#user.id"),
@CachePut(value = "mycache2", key = "#user.username.concat(#user.email)")
},
evict = {
@CacheEvict(value = "tempcache", key = "#user.id")
}
)
public User save(User user) {
.............
}
上述配置表示:在保存一个用户信息的时候,以user.id为key将该user缓存在mycache中,并以user.username+user.email为key将该user缓存在mycache2中,同时从tempcache中删除key值为user.id的缓存。
名字 | 位置 | 描述 | 示例 |
methodName |
root对象 |
当前被调用的方法名 |
|
method |
root对象 |
当前被调用的方法 |
|
target |
root对象 |
当前被调用的目标对象 |
|
targetClass |
root对象 |
当前被调用的目标对象类 |
|
args |
root对象 |
当前被调用的方法的参数列表 |
|
caches |
root对象 |
当前方法调用使用的缓存列表(如@Cacheable(value={"cache1", "cache2"})),则有两个cache |
|
argument name |
执行上下文 |
当前被调用的方法的参数,如findById(Long id),我们可以通过#id拿到参数 |
#user.id |
result |
执行上下文 |
方法执行后的返回值(仅当方法执行之后的判断有效,如‘unless’,'cache evict'的beforeInvocation=false) |
|
通过这些数据我们可能实现比较复杂的缓存逻辑。例如上面的condition中定义的:
#username.concat(#email)
#user.id ne 12
#username eq 'wangd'
等等都是使用了上下文数据的SpEL表达式。
private final Set annotationParsers;
public AnnotationCacheOperationSource() {
this(true);
}
public AnnotationCacheOperationSource(boolean publicMethodsOnly) {
this.publicMethodsOnly = publicMethodsOnly;
this.annotationParsers = new LinkedHashSet(1);
this.annotationParsers.add(new SpringCacheAnnotationParser());
}
@Override
protected Collection findCacheOperations(final Class> clazz) {
return determineCacheOperations(new CacheOperationProvider() {
@Override
public Collection getCacheOperations(CacheAnnotationParser parser) {
return parser.parseCacheAnnotations(clazz);
}
});
}
@Override
protected Collection findCacheOperations(final Method method) {
return determineCacheOperations(new CacheOperationProvider() {
@Override
public Collection getCacheOperations(CacheAnnotationParser parser) {
return parser.parseCacheAnnotations(method);
}
});
}
protected Collection determineCacheOperations(CacheOperationProvider provider) {
Collection ops = null;
for (CacheAnnotationParser annotationParser : this.annotationParsers) {
Collection annOps = provider.getCacheOperations(annotationParser);
if (annOps != null) {
if (ops == null) {
ops = new ArrayList();
}
ops.addAll(annOps);
}
}
return ops;
}
/**
* Callback interface providing {@link CacheOperation} instance(s) based on
* a given {@link CacheAnnotationParser}.
*/
protected interface CacheOperationProvider {
/**
* Returns the {@link CacheOperation} instance(s) provided by the specified parser.
*
* @param parser the parser to use
* @return the cache operations or {@code null} if none is found
*/
Collection getCacheOperations(CacheAnnotationParser parser);
}
该实现中使用回调模式,用Set @Override
public Collection parseCacheAnnotations(Method method) {
DefaultCacheConfig defaultConfig = getDefaultCacheConfig(method.getDeclaringClass());
return parseCacheAnnotations(defaultConfig, method);
}
来获取方法上的Cache相关注释,并将其封装成对应的CacheOperation集合并返回。其中:
DefaultCacheConfig defaultConfig = getDefaultCacheConfig(method.getDeclaringClass());
首先判断方法所在的类上是否配置了@CacheConfig注释,如果有就将该注释中的属性值设置到defaultConfig类中,用于后续配置。然后调用核心方法: protected Collection parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae) {
Collection ops = null;
Collection cacheables = AnnotatedElementUtils.findAllMergedAnnotations(ae, Cacheable.class);
if (!cacheables.isEmpty()) {
ops = lazyInit(ops);
for (Cacheable cacheable : cacheables) {
ops.add(parseCacheableAnnotation(ae, cachingConfig, cacheable));
}
}
Collection evicts = AnnotatedElementUtils.findAllMergedAnnotations(ae, CacheEvict.class);
if (!evicts.isEmpty()) {
ops = lazyInit(ops);
for (CacheEvict evict : evicts) {
ops.add(parseEvictAnnotation(ae, cachingConfig, evict));
}
}
Collection puts = AnnotatedElementUtils.findAllMergedAnnotations(ae, CachePut.class);
if (!puts.isEmpty()) {
ops = lazyInit(ops);
for (CachePut put : puts) {
ops.add(parsePutAnnotation(ae, cachingConfig, put));
}
}
Collection cachings = AnnotatedElementUtils.findAllMergedAnnotations(ae, Caching.class);
if (!cachings.isEmpty()) {
ops = lazyInit(ops);
for (Caching caching : cachings) {
Collection cachingOps = parseCachingAnnotation(ae, cachingConfig, caching);
if (cachingOps != null) {
ops.addAll(cachingOps);
}
}
}
return ops;
}
该方法中使用Spring Core模块的AnnotatedElementUtils来得到标注到可被标注对象(在这里包括类和方法)上的指定类型的注释,包括了注释上再打注释等层级结构以及层级属性的合并操作。 CacheableOperation parseCacheableAnnotation(AnnotatedElement ae, DefaultCacheConfig defaultConfig, Cacheable cacheable) {
CacheableOperation.Builder builder = new CacheableOperation.Builder();
builder.setName(ae.toString());
builder.setCacheNames(cacheable.cacheNames());
builder.setCondition(cacheable.condition());
builder.setUnless(cacheable.unless());
builder.setKey(cacheable.key());
builder.setKeyGenerator(cacheable.keyGenerator());
builder.setCacheManager(cacheable.cacheManager());
builder.setCacheResolver(cacheable.cacheResolver());
builder.setSync(cacheable.sync());
defaultConfig.applyDefault(builder);
CacheableOperation op = builder.build();
validateCacheOperation(ae, op);
return op;
}
利用CacheableOperation.Builder来构建一个CacheableOperation,并添加到CollectionCollection parseCachingAnnotation(AnnotatedElement ae, DefaultCacheConfig defaultConfig, Caching caching) {
Collection ops = null;
Cacheable[] cacheables = caching.cacheable();
if (!ObjectUtils.isEmpty(cacheables)) {
ops = lazyInit(ops);
for (Cacheable cacheable : cacheables) {
ops.add(parseCacheableAnnotation(ae, defaultConfig, cacheable));
}
}
CacheEvict[] cacheEvicts = caching.evict();
if (!ObjectUtils.isEmpty(cacheEvicts)) {
ops = lazyInit(ops);
for (CacheEvict cacheEvict : cacheEvicts) {
ops.add(parseEvictAnnotation(ae, defaultConfig, cacheEvict));
}
}
CachePut[] cachePuts = caching.put();
if (!ObjectUtils.isEmpty(cachePuts)) {
ops = lazyInit(ops);
for (CachePut cachePut : cachePuts) {
ops.add(parsePutAnnotation(ae, defaultConfig, cachePut));
}
}
return ops;
}
其中对caching的cacheable、evict及put属性对应的各组@Cacheable、@CacheEvict及@CachePut注释分别调用前面介绍的执行逻辑来构建相应的CacheOperation并添加到Collection当调用其方法的时候会通过代理执行到BeanFactoryCacheOperationSourceAdvisor定义的切面。该切面是一个PointcutAdvisor,在SpringAOP底层框架DefaultAdvisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice方法中使用
pointcutAdvisor.getPointcut().getMethodMatcher().matches(method, targetClass))
来判断被调用方法是否匹配切点逻辑,如果匹配就执行其拦截器中的逻辑,BeanFactoryCacheOperationSourceAdvisor中注册的拦截器是CacheInterceptor,其执行逻辑为:
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
CacheOperationInvoker aopAllianceInvoker = new CacheOperationInvoker() {
@Override
public Object invoke() {
try {
return invocation.proceed();
}
catch (Throwable ex) {
throw new ThrowableWrapper(ex);
}
}
};
try {
return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
}
catch (CacheOperationInvoker.ThrowableWrapper th) {
throw th.getOriginal();
}
}
其中Spring AOP底层调用该方法时传递来的参数MethodInvocation invocation是一个把调用方法method与其对应的切面拦截器interceptors组装成类似于ChainFilter一样的结构。 protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
// Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically)
if (this.initialized) {
Class> targetClass = getTargetClass(target);
Collection operations = getCacheOperationSource().getCacheOperations(method, targetClass);
if (!CollectionUtils.isEmpty(operations)) {
return execute(invoker, method, new CacheOperationContexts(operations, method, args, target, targetClass));
}
}
return invoker.invoke();
}
该方法首先通过前面描述的AnnotationCacheOperationSource.getCacheOperations(method, targetClass)来获得调用方法上的Collection public CacheOperationContexts(Collection extends CacheOperation> operations, Method method,
Object[] args, Object target, Class> targetClass) {
for (CacheOperation operation : operations) {
this.contexts.add(operation.getClass(), getOperationContext(operation, method, args, target, targetClass));
}
this.sync = determineSyncFlag(method);
}
其中,为每个operation分别创建CacheOperationContext:
protected CacheOperationContext getOperationContext(
CacheOperation operation, Method method, Object[] args, Object target, Class> targetClass) {
CacheOperationMetadata metadata = getCacheOperationMetadata(operation, method, targetClass);
return new CacheOperationContext(metadata, args, target);
}
获取CacheOperationMetadata metadata时的较重要的动作就是获取CacheOperation中用String名称定义的CacheResolver和KeyGenerator的bean。 public CacheOperationContext(CacheOperationMetadata metadata, Object[] args, Object target) {
this.metadata = metadata;
this.args = extractArgs(metadata.method, args);
this.target = target;
this.caches = CacheAspectSupport.this.getCaches(this, metadata.cacheResolver);
this.cacheNames = createCacheNames(this.caches);
this.methodCacheKey = new AnnotatedElementKey(metadata.method, metadata.targetClass);
}
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts)
该方法执行逻辑如下:
#首先判断是否需要在方法调用前执行缓存清除:
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
CacheOperationExpressionEvaluator.NO_RESULT);
其作用是判断是否需要在方法调用前执行缓存清除。判断是否存在beforeInvocation==true并且condition符合条件的@CacheEvict注释,如果存在则最终执行方法:
private void performCacheEvict(CacheOperationContext context, CacheEvictOperation operation, Object result) {
Object key = null;
for (Cache cache : context.getCaches()) {
if (operation.isCacheWide()) {
logInvalidating(context, operation, null);
doClear(cache);
}
else {
if (key == null) {
key = context.generateKey(result);
}
logInvalidating(context, operation, key);
doEvict(cache, key);
}
}
}
对于注释中定义的每一个cache都根据allEntries是否为true执行其clear()方法或evict(key)方法来清除全部或部分缓存。
#然后检查是否能得到一个符合条件的缓存值:
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
其中调用了cache.get(key),在此就不再贴出。
#随后如果Cacheable miss(没有获取到缓存),就会创建一个对应的CachePutRequest并收集起来:
List cachePutRequests = new LinkedList();
if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class),
CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}
注意,此时方法尚未执行,因此第二个参数为CacheOperationExpressionEvaluator.NO_RESULT,意思是当前上下文中#result不存在。#接下来判断返回缓存值还是实际调用方法的结果
如果得到了缓存,并且CachePutRequests为空并且不含有符合条件(condition match)的@CachePut注释,那么就将returnValue赋值为缓存值;否则实际执行方法,并将returnValue赋值为方法返回值。
Object cacheValue;
Object returnValue;
if (cacheHit != null && cachePutRequests.isEmpty() && !hasCachePut(contexts)) {
// If there are no put requests, just use the cache hit
cacheValue = cacheHit.get();
if (method.getReturnType() == javaUtilOptionalClass &&
(cacheValue == null || cacheValue.getClass() != javaUtilOptionalClass)) {
returnValue = OptionalUnwrapper.wrap(cacheValue);
}
else {
returnValue = cacheValue;
}
}
else {
// Invoke the method if we don't have a cache hit
returnValue = invokeOperation(invoker);
if (returnValue != null && returnValue.getClass() == javaUtilOptionalClass) {
cacheValue = OptionalUnwrapper.unwrap(returnValue);
}
else {
cacheValue = returnValue;
}
}
#方法调用后收集@CachePut明确定义的CachePutRequest
收集符合条件的@CachePut定义的CachePutRequest,并添加到上面的cachePutRequests中:
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
注意,此时处于方法调用后,返回结果已经存在了,因此在condition定义中可以使用上下文#result。
#执行CachePutRequest将符合条件的数据写入缓存
对于上面收集到的cachePutRequests,逐个调用其apply(cacheValue)方法:
for (CachePutRequest cachePutRequest : cachePutRequests) {
cachePutRequest.apply(cacheValue);
}
其中,CachePutRequest.apply方法首先判断unless条件,unless不符合的时候才会对operationContext中的每一个cache执行put动作:
public void apply(Object result) {
if (this.context.canPutToCache(result)) {
for (Cache cache : this.context.getCaches()) {
doPut(cache, this.key, result);
}
}
}
判断是否执行put动作的方法如下:
protected boolean canPutToCache(Object value) {
String unless = "";
if (this.metadata.operation instanceof CacheableOperation) {
unless = ((CacheableOperation) this.metadata.operation).getUnless();
}
else if (this.metadata.operation instanceof CachePutOperation) {
unless = ((CachePutOperation) this.metadata.operation).getUnless();
}
if (StringUtils.hasText(unless)) {
EvaluationContext evaluationContext = createEvaluationContext(value);
return !evaluator.unless(unless, this.methodCacheKey, evaluationContext);
}
return true;
}
#最后判断是否需要在方法调用后执行缓存清除:
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
与步骤(1)相对应,其作用是判断是否需要在方法调用后执行缓存清除。判断是否存在beforeInvocation==false并且condition符合条件的@CacheEvict注释,注意此时上下文中结果#result是可用的。如果存在则最终执行缓存清除逻辑。
小结:至此,SpringCache基本概念、实现原理及业务逻辑就为大家全部介绍完毕了,具体的使用及最佳实践还是需要各自针对具体的应用场景来摸索的,没有一劳永逸的解决方案。但在了解底层的实现原理及业务逻辑以后,再去配置、使用甚至定制,都会变得游刃有余了。
相关文章:SpringCache实现原理及核心业务逻辑(一)
相关文章:SpringCache实现原理及核心业务逻辑(二)