介绍
这篇文章将演示如何仅使用Spring AOP实现任何数据源的请求级可重复读取。
Spring缓存
Spring提供了非常有用的缓存抽象 ,允许您将应用程序逻辑与缓存实现细节分离。
Spring Caching使用应用程序级范围,因此对于仅请求的备忘录,我们需要采用DIY方法。
请求级缓存
请求级缓存条目生命周期始终绑定到当前请求范围。 这种缓存与提供会话级可重复读取的 Hibernate Persistence Context非常相似。
为了防止更新丢失 ,甚至对于NoSQL解决方案,必须进行可重复的读取 。
分步实施
首先,我们将定义一个“记忆标记”注释:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Memoize {
}
该注释将显式标记所有需要记住的方法。
为了区分不同的方法调用,我们将方法调用信息封装为以下对象类型:
public class InvocationContext {
public static final String TEMPLATE = "%s.%s(%s)";
private final Class targetClass;
private final String targetMethod;
private final Object[] args;
public InvocationContext(Class targetClass, String targetMethod, Object[] args) {
this.targetClass = targetClass;
this.targetMethod = targetMethod;
this.args = args;
}
public Class getTargetClass() {
return targetClass;
}
public String getTargetMethod() {
return targetMethod;
}
public Object[] getArgs() {
return args;
}
@Override
public boolean equals(Object that) {
return EqualsBuilder.reflectionEquals(this, that);
}
@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
@Override
public String toString() {
return String.format(TEMPLATE, targetClass.getName(), targetMethod, Arrays.toString(args));
}
}
很少有人知道Spring Request / Session bean的作用域。
因为我们需要一个请求级的备忘录作用域,所以我们可以使用Spring请求作用域来简化我们的设计,该作用域隐藏了实际的HttpSession解析逻辑:
@Component
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS, value = "request")
public class RequestScopeCache {
public static final Object NONE = new Object();
private final Map<InvocationContext, Object> cache = new HashMap<InvocationContext, Object>();
public Object get(InvocationContext invocationContext) {
return cache.containsKey(invocationContext) ? cache.get(invocationContext) : NONE;
}
public void put(InvocationContext methodInvocation, Object result) {
cache.put(methodInvocation, result);
}
}
由于没有运行时处理引擎,仅注释就没有任何意义,因此,我们必须定义一个实现实际备注逻辑的Spring Aspect:
@Aspect
public class MemoizerAspect {
@Autowired
private RequestScopeCache requestScopeCache;
@Around("@annotation(com.vladmihalcea.cache.Memoize)")
public Object memoize(ProceedingJoinPoint pjp) throws Throwable {
InvocationContext invocationContext = new InvocationContext(
pjp.getSignature().getDeclaringType(),
pjp.getSignature().getName(),
pjp.getArgs()
);
Object result = requestScopeCache.get(invocationContext);
if (RequestScopeCache.NONE == result) {
result = pjp.proceed();
LOGGER.info("Memoizing result {}, for method invocation: {}", result, invocationContext);
requestScopeCache.put(invocationContext, result);
} else {
LOGGER.info("Using memoized result: {}, for method invocation: {}", result, invocationContext);
}
return result;
}
}
测试时间
让我们对所有这些进行测试。 为了简单起见,我们将使用Fibonacci数字计算器模拟请求级范围的备忘需求:
@Component
public class FibonacciServiceImpl implements FibonacciService {
@Autowired
private ApplicationContext applicationContext;
private FibonacciService fibonacciService;
@PostConstruct
private void init() {
fibonacciService = applicationContext.getBean(FibonacciService.class);
}
@Memoize
public int compute(int i) {
LOGGER.info("Calculate fibonacci for number {}", i);
if (i == 0 || i == 1)
return i;
return fibonacciService.compute(i - 2) + fibonacciService.compute(i - 1);
}
}
如果我们要计算第十个斐波那契数,我们将得到以下结果:
Calculate fibonacci for number 10
Calculate fibonacci for number 8
Calculate fibonacci for number 6
Calculate fibonacci for number 4
Calculate fibonacci for number 2
Calculate fibonacci for number 0
Memoizing result 0, for method invocation: com.vladmihalcea.cache.FibonacciService.compute([0])
Calculate fibonacci for number 1
Memoizing result 1, for method invocation: com.vladmihalcea.cache.FibonacciService.compute([1])
Memoizing result 1, for method invocation: com.vladmihalcea.cache.FibonacciService.compute([2])
Calculate fibonacci for number 3
Using memoized result: 1, for method invocation: com.vladmihalcea.cache.FibonacciService.compute([1])
Using memoized result: 1, for method invocation: com.vladmihalcea.cache.FibonacciService.compute([2])
Memoizing result 2, for method invocation: com.vladmihalcea.cache.FibonacciService.compute([3])
Memoizing result 3, for method invocation: com.vladmihalcea.cache.FibonacciService.compute([4])
Calculate fibonacci for number 5
Using memoized result: 2, for method invocation: com.vladmihalcea.cache.FibonacciService.compute([3])
Using memoized result: 3, for method invocation: com.vladmihalcea.cache.FibonacciService.compute([4])
Memoizing result 5, for method invocation: com.vladmihalcea.cache.FibonacciService.compute([5])
Memoizing result 8, for method invocation: com.vladmihalcea.cache.FibonacciService.compute([6])
Calculate fibonacci for number 7
Using memoized result: 5, for method invocation: com.vladmihalcea.cache.FibonacciService.compute([5])
Using memoized result: 8, for method invocation: com.vladmihalcea.cache.FibonacciService.compute([6])
Memoizing result 13, for method invocation: com.vladmihalcea.cache.FibonacciService.compute([7])
Memoizing result 21, for method invocation: com.vladmihalcea.cache.FibonacciService.compute([8])
Calculate fibonacci for number 9
Using memoized result: 13, for method invocation: com.vladmihalcea.cache.FibonacciService.compute([7])
Using memoized result: 21, for method invocation: com.vladmihalcea.cache.FibonacciService.compute([8])
Memoizing result 34, for method invocation: com.vladmihalcea.cache.FibonacciService.compute([9])
Memoizing result 55, for method invocation: com.vladmihalcea.cache.FibonacciService.compute([10])
结论
备注是一个贯穿各领域的问题,Spring AOP允许您将缓存详细信息与实际的应用程序逻辑代码分离。
- 代码可在GitHub上获得 。
翻译自: https://www.javacodegeeks.com/2014/12/spring-request-level-memoization.html