介绍
记忆化是一种方法级别的缓存技术,用于加快连续调用的速度。
这篇文章将演示如何仅使用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;}@Overridepublic boolean equals(Object that) {return EqualsBuilder.reflectionEquals(this, that);}@Overridepublic int hashCode() {return HashCodeBuilder.reflectionHashCode(this);}@Overridepublic 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 {@Autowiredprivate 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 {@Autowiredprivate ApplicationContext applicationContext;private FibonacciService fibonacciService;@PostConstructprivate void init() {fibonacciService = applicationContext.getBean(FibonacciService.class);}@Memoizepublic 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