替换Spring Boot 的EnableCaching注解

时间:2022-01-28 23:27:06

SpringBoot 中可使用@Cacheable注解来更方便的使用redis,这个注解是通过拦截器工作的,使用了@Cacheable的方法执行时,执行到CglibAopProxy.java中的 DynamicAdvisedInterceptor.intercept方法中如下图位置时,会发现CacheInterceptor:

替换Spring Boot 的EnableCaching注解

CacheInterceptor是由EnableCaching注解引入的:

替换Spring Boot 的EnableCaching注解

CachingConfigurationSelector:

替换Spring Boot 的EnableCaching注解

注意上图中的ProxyCachingConfiguration:

替换Spring Boot 的EnableCaching注解

方法的返回值如果缓存中存在直接返回缓存中结果,缓存中没有才会实际执行方法这个功能的实现就是在CacheInterceptor拦截器中了,它的invoke方法:

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

具体读取数据在protected的execute方法中的private的execute中:

	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<CacheOperation> operations = getCacheOperationSource().getCacheOperations(method, targetClass);
if (!CollectionUtils.isEmpty(operations)) {
return execute(invoker, method, new CacheOperationContexts(operations, method, args, target, targetClass));
}
} return invoker.invoke();
}

替换Spring Boot 的EnableCaching注解

首先下面代码第一行findCachedItem方法判断缓存中是否有数据,然后在后面的那个判断中判断,有就取缓存并直接返回结果,不再执行被拦截方法;否则else执行被拦截方法,被拦截方法中一般就是读数据库了,不过这就和这部分没啥关系了。

		Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

		// Collect puts from any @Cacheable miss, if no cached item is found
List<CachePutRequest> cachePutRequests = new LinkedList<CachePutRequest>();
if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class),
CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
} 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();
returnValue = wrapCacheValue(method, cacheValue);
}
else {
// Invoke the method if we don't have a cache hit
returnValue = invokeOperation(invoker);
cacheValue = unwrapReturnValue(returnValue);
}

我这是用redis做缓存的,通过上面代码可以发现,一旦redis挂了,findCachedItem方法报异常,被拦截的方法就不能正常使用了,那么我需要redis出异常了,正常走数据库该怎么办呢。我首先考虑的是重写protected的那个execute方法,然而发现有一个private的内部类绕不过去,全都自己实现一遍,完全没必要,因为懒得细调了于是我就新建了一个拦截器,继承CacheInterceptor:

    @Override
protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
try {
return super.execute(invoker, target, method, args);
}catch (Exception e){
return invokeOperation(invoker);
}
}

然后就是让新拦截器起作用,我讨厌SpringBoot的注解就讨厌在这部分。如果还用EnableCaching注解这问题就复杂了,于是简单粗暴新建注解代替EnableCaching,然后CachingConfigurationSelector也需要替换掉。接着是ProxyCachingConfiguration以及它的基类AbstractCachingConfiguration:

替换Spring Boot 的EnableCaching注解

重写这个方法,用新建的注解替换掉方法中的EnableCaching就可以了,由于用的是线上代码测试的就不好往这贴了,不过亲测成功,虽然方法比较粗糙,但是SpringBoot里的代码留的扩展余地就那样,也不想太费工夫了。

==========================================================

咱最近用的github:https://github.com/saaavsaaa

微信公众号:

                      替换Spring Boot 的EnableCaching注解