还有其他方法,但以下方法可能是有效的解决方案。
第一步将是定义一个自定义异常,以便稍后能够适当地处理它。此异常将接收缓存的名称和要清除的键等参数。例如:
public class CauseOfEvictionException extends RuntimeException {
public CauseOfEvictionException(String message, String cacheName, String cacheKey) {
super(message);
this.cacheName = cacheName;
this.cacheKey = cacheKey;
}
}
在您的示例中,您的B
类将引发此异常:
@Service
Class A{
@Autowired
B b;
@Transactional
public List<Data> getAllBusinessData(){
List<Data> dataList = b.getDataFromSystem("key");
throw new CauseOfEvictionException("test", "cacheName", "key");
}
}
现在,我们需要一种处理这种异常的方法。
独立于该方法之外,想法是负责处理异常的代码将访问配置的
CacheManager
并触发缓存清除。
因为您正在使用Spring Boot,处理它的简单方法是通过扩展
ResponseEntityExceptionHandler
来提供适当的
@ExceptionHandler
。请参考更多信息
我在相关SO问题中提供的答案或
这篇很棒的文章。
总之,请考虑以下示例:
@ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
@Autowired
private CacheManager cacheManager;
@ExceptionHandler(CauseOfEvictionException.class)
public ResponseEntity<Object> handleCauseOfEvictionException(
CauseOfEvictionException e) {
this.cacheManager.getCache(e.getCacheName()).evict(e.getCacheKey());
return ...;
}
}
重要的是要意识到,当处理由多个参数默认情况下(请考虑阅读此文)组成的键时,实际缓存键将被包装为SimpleKey
类的实例,该实例包含所有这些参数。
请注意,此默认行为可以通过使用SpEL或提供自己的缓存KeyGenerator进行某种程度的
定制化。参考
这里,这是框架提供的默认实现
SimpleKeyGenerator
。
考虑问题,可能的解决方案也可以使用某种AOP。思路如下。
首先,定义某种辅助注释。该注释将有助于确定应该建议哪些方法。例如:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EvictCacheOnError {
}
下一步是定义处理实际缓存逐出过程的方面。假设您只需要通知Spring管理的bean,为了简单起见,我们可以使用
Spring AOP。您可以使用
@Around
或
@AfterThrowing
方面。考虑以下示例:
@Aspect
@Component
public class EvictCacheOnErrorAspect {
@Autowired
private CacheManager cacheManager;
@Around("@annotation(your.pkg.EvictCacheOnError)")
public void evictCacheOnError(ProceedingJoinPoint pjp) {
try {
Object retVal = pjp.proceed();
return retVal;
} catch (CauseOfEvictionException e) {
this.cacheManager.getCache(
e.getCacheName()).evict(e.getCacheKey()
);
throw e;
}
}
}
最后一步是注释应用行为的方法:
@Service
Class A{
@Autowired
B b;
@Transactional
@EvictCacheOnError
public List<Data> getAllBusinessData(){
List<Data> dataList = b.getDataFromSystem("key");
throw new CauseOfEvictionException("test", "cacheName", "key");
}
}
您甚至可以尝试泛化这个想法,通过在EvictCacheOnError
注释中提供所有必要的信息来执行缓存清除:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EvictCacheOnError {
String cacheName();
int[] cacheKeyArgsIndexes();
}
以下是:
@Aspect
@Component
public class EvictCacheOnErrorAspect {
@Autowired
private CacheManager cacheManager;
@Autowired
private KeyGenerator keyGenerator;
@Around("@annotation(your.pkg.EvictCacheOnError)")
public void evictCacheOnError(ProceedingJoinPoint pjp) {
try {
Object retVal = pjp.proceed();
return retVal;
} catch (Throwable t) {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
EvictCacheOnError evictCacheOnError = method.getAnnotation(EvictCacheOnError.class);
int[] cacheKeyArgsIndexes = evictCacheOnError.cacheKeyArgsIndexes();
Object[] args = pjp.getArgs();
List<Object> cacheKeyArgsList = new ArrayList<>(cacheKeyArgsIndexes.length);
for (int i=0; i < cacheKeyArgsIndexes.length; i++) {
cacheKeyArgsList.add(args[cacheKeyArgsIndexes[i]]);
}
Object[] cacheKeyArgs = new Object[cacheKeyArgsList.size()];
cacheKeyArgsList.toArray(cacheKeyArgs);
Object target = pjp.getTarget();
Object cacheKey = this.keyGenerator.generate(target, method, cacheKeyArgs);
String cacheName = evictCacheOnError.cacheName();
this.cacheManager.getCache(cacheName).evict(cacheKey);
throw new RuntimeException(t);
}
}
}
这种解决方案取决于实际的方法参数,如果缓存键是基于方法体内获得的中间结果,则可能不适用。