Spring AOP配置:
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Cacheable {
/**
* The cache key.
*
* @return
*/
String key() default "";
/**
* The cache timeout, unit for minute.
*
* @return
*/
int timeout() default 30;
/**
* Whether serialize the cache object.
*
* @return
*/
boolean serialize() default false;
}
public class CacheAdvice extends CacheAdviceSupport {
public Object cacheData(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method targetMethod = AopUtils.getMostSpecificMethod(methodSignature.getMethod(), joinPoint.getTarget().getClass());
CacheOperationContext context = getOperationContext(targetMethod, joinPoint.getArgs(), joinPoint.getTarget(), joinPoint.getTarget().getClass());
Object key = generateKey(cacheable, context);
Object cacheObject = getCacheObject(cacheable, key);
if (null != cacheObject) {
return cacheObject;
}
Object result = joinPoint.proceed();
if (null != result) {
cacheObject(cacheable, key, result);
}
return result;
}
private Object getCacheObject(Cacheable cacheable, Object key) {
Object cacheObject = cache.get(key);
if (cacheable.serialize()) {
cacheObject = SerializationUtils.deserialize((byte[]) cacheObject);
}
return cacheObject;
}
private void cacheObject(Cacheable cacheable, Object key, Object value) {
if (cacheable.serialize()) {
value = SerializationUtils.serialize(value);
}
cache.put(key, value, cacheable.timeout());
}
}
public abstract class CacheAdviceSupport {
private final ExpressionEvaluator evaluator = new ExpressionEvaluator();
private KeyGenerator keyGenerator = new SimpleKeyGenerator();
protected Cache cache;
/**
* Compute the key for the given caching operation.
*
* @return the generated key, or {@code null} if none can be generated
*/
protected Object generateKey(Cacheable cacheable, CacheOperationContext context) {
return context.generateKey(cacheable, ExpressionEvaluator.NO_RESULT);
}
protected CacheOperationContext getOperationContext(Method method, Object[] args, Object target, Class> targetClass) {
return new CacheOperationContext(method, args, target, targetClass);
}
protected class CacheOperationContext {
private final Method method;
private final Object[] args;
private final Object target;
private final Class> targetClass;
private final MethodCacheKey methodCacheKey;
public CacheOperationContext(Method method, Object[] args, Object target, Class> targetClass) {
this.method = method;
this.args = extractArgs(method, args);
this.target = target;
this.targetClass = targetClass;
this.methodCacheKey = new MethodCacheKey(method, targetClass);
}
public Object getTarget() {
return this.target;
}
public Method getMethod() {
return method;
}
public Object[] getArgs() {
return this.args;
}
private Object[] extractArgs(Method method, Object[] args) {
if (!method.isVarArgs()) {
return args;
}
Object[] varArgs = ObjectUtils.toObjectArray(args[args.length - 1]);
Object[] combinedArgs = new Object[args.length - 1 + varArgs.length];
System.arraycopy(args, 0, combinedArgs, 0, args.length - 1);
System.arraycopy(varArgs, 0, combinedArgs, args.length - 1, varArgs.length);
return combinedArgs;
}
/**
* Compute the key for the given caching operation.
*
* @return the generated key, or {@code null} if none can be generated
*/
public Object generateKey(Cacheable cacheable, Object result) {
if (StringUtils.hasText(cacheable.key())) {
EvaluationContext evaluationContext = createEvaluationContext(result);
return evaluator.key(cacheable.key(), this.methodCacheKey, evaluationContext);
}
return keyGenerator.generate(this.target, this.method, this.args);
}
private EvaluationContext createEvaluationContext(Object result) {
return evaluator.createEvaluationContext(Arrays.asList(new Cache[] { cache }), this.method, this.args, this.target, this.targetClass, result);
}
}
/**
* @param keyGenerator the keyGenerator to set
*/
public void setKeyGenerator(KeyGenerator keyGenerator) {
this.keyGenerator = keyGenerator;
}
/**
* @param cache the cache to set
*/
public void setCache(Cache cache) {
this.cache = cache;
}
}
class ExpressionEvaluator {
/**
* Indicate that there is no result variable.
*/
public static final Object NO_RESULT = new Object();
/**
* Indicate that the result variable cannot be used at all.
*/
public static final Object RESULT_UNAVAILABLE = new Object();
/**
* The name of the variable holding the result object.
*/
public static final String RESULT_VARIABLE = "result";
private final SpelExpressionParser parser = new SpelExpressionParser();
// shared param discoverer since it caches data internally
private final ParameterNameDiscoverer paramNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
private final Map keyCache = new ConcurrentHashMap(64);
private final Map conditionCache = new ConcurrentHashMap(64);
private final Map unlessCache = new ConcurrentHashMap(64);
private final Map targetMethodCache = new ConcurrentHashMap(64);
/**
* Create an {@link EvaluationContext} without a return value.
* @see #createEvaluationContext(Collection, Method, Object[], Object, Class, Object)
*/
public EvaluationContext createEvaluationContext(Collection extends Cache> caches,
Method method, Object[] args, Object target, Class> targetClass) {
return createEvaluationContext(caches, method, args, target, targetClass, NO_RESULT);
}
/**
* Create an {@link EvaluationContext}.
* @param caches the current caches
* @param method the method
* @param args the method arguments
* @param target the target object
* @param targetClass the target class
* @param result the return value (can be {@code null}) or
* {@link #NO_RESULT} if there is no return at this time
* @return the evaluation context
*/
public EvaluationContext createEvaluationContext(Collection extends Cache> caches,
Method method, Object[] args, Object target, Class> targetClass, Object result) {
CacheExpressionRootObject rootObject = new CacheExpressionRootObject(caches,
method, args, target, targetClass);
CacheEvaluationContext evaluationContext = new CacheEvaluationContext(rootObject,
this.paramNameDiscoverer, method, args, targetClass, this.targetMethodCache);
if (result == RESULT_UNAVAILABLE) {
evaluationContext.addUnavailableVariable(RESULT_VARIABLE);
}
else if (result != NO_RESULT) {
evaluationContext.setVariable(RESULT_VARIABLE, result);
}
return evaluationContext;
}
public Object key(String keyExpression, MethodCacheKey methodKey, EvaluationContext evalContext) {
return getExpression(this.keyCache, keyExpression, methodKey).getValue(evalContext);
}
public boolean condition(String conditionExpression, MethodCacheKey methodKey, EvaluationContext evalContext) {
return getExpression(this.conditionCache, conditionExpression, methodKey).getValue(evalContext, boolean.class);
}
public boolean unless(String unlessExpression, MethodCacheKey methodKey, EvaluationContext evalContext) {
return getExpression(this.unlessCache, unlessExpression, methodKey).getValue(evalContext, boolean.class);
}
private Expression getExpression(Map cache, String expression, MethodCacheKey methodKey) {
ExpressionKey key = createKey(methodKey, expression);
Expression expr = cache.get(key);
if (expr == null) {
expr = this.parser.parseExpression(expression);
cache.put(key, expr);
}
return expr;
}
private ExpressionKey createKey(MethodCacheKey methodCacheKey, String expression) {
return new ExpressionKey(methodCacheKey, expression);
}
private static class ExpressionKey {
private final MethodCacheKey methodCacheKey;
private final String expression;
public ExpressionKey(MethodCacheKey methodCacheKey, String expression) {
this.methodCacheKey = methodCacheKey;
this.expression = expression;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof ExpressionKey)) {
return false;
}
ExpressionKey otherKey = (ExpressionKey) other;
return (this.methodCacheKey.equals(otherKey.methodCacheKey) &&
ObjectUtils.nullSafeEquals(this.expression, otherKey.expression));
}
@Override
public int hashCode() {
return this.methodCacheKey.hashCode() * 29 + (this.expression != null ? this.expression.hashCode() : 0);
}
}
}
class CacheExpressionRootObject {
private final Collection extends Cache> caches;
private final Method method;
private final Object[] args;
private final Object target;
private final Class> targetClass;
public CacheExpressionRootObject(
Collection extends Cache> caches, Method method, Object[] args, Object target, Class> targetClass) {
Assert.notNull(method, "Method is required");
Assert.notNull(targetClass, "targetClass is required");
this.method = method;
this.target = target;
this.targetClass = targetClass;
this.args = args;
this.caches = caches;
}
public Collection extends Cache> getCaches() {
return this.caches;
}
public Method getMethod() {
return this.method;
}
public String getMethodName() {
return this.method.getName();
}
public Object[] getArgs() {
return this.args;
}
public Object getTarget() {
return this.target;
}
public Class> getTargetClass() {
return this.targetClass;
}
}
class CacheEvaluationContext extends StandardEvaluationContext {
private final ParameterNameDiscoverer paramDiscoverer;
private final Method method;
private final Object[] args;
private final Class> targetClass;
private final Map methodCache;
private final List unavailableVariables;
private boolean paramLoaded = false;
CacheEvaluationContext(Object rootObject, ParameterNameDiscoverer paramDiscoverer, Method method,
Object[] args, Class> targetClass, Map methodCache) {
super(rootObject);
this.paramDiscoverer = paramDiscoverer;
this.method = method;
this.args = args;
this.targetClass = targetClass;
this.methodCache = methodCache;
this.unavailableVariables = new ArrayList();
}
/**
* Add the specified variable name as unavailable for that context. Any expression trying
* to access this variable should lead to an exception.
* This permits the validation of expressions that could potentially a variable even
* when such variable isn't available yet. Any expression trying to use that variable should
* therefore fail to evaluate.
*/
public void addUnavailableVariable(String name) {
this.unavailableVariables.add(name);
}
/**
* Load the param information only when needed.
*/
@Override
public Object lookupVariable(String name) {
if (this.unavailableVariables.contains(name)) {
throw new VariableNotAvailableException(name);
}
Object variable = super.lookupVariable(name);
if (variable != null) {
return variable;
}
if (!this.paramLoaded) {
loadArgsAsVariables();
this.paramLoaded = true;
variable = super.lookupVariable(name);
}
return variable;
}
private void loadArgsAsVariables() {
// shortcut if no args need to be loaded
if (ObjectUtils.isEmpty(this.args)) {
return;
}
MethodCacheKey methodKey = new MethodCacheKey(this.method, this.targetClass);
Method targetMethod = this.methodCache.get(methodKey);
if (targetMethod == null) {
targetMethod = AopUtils.getMostSpecificMethod(this.method, this.targetClass);
if (targetMethod == null) {
targetMethod = this.method;
}
this.methodCache.put(methodKey, targetMethod);
}
// save arguments as indexed variables
for (int i = 0; i < this.args.length; i++) {
setVariable("a" + i, this.args[i]);
setVariable("p" + i, this.args[i]);
}
String[] parameterNames = this.paramDiscoverer.getParameterNames(targetMethod);
// save parameter names (if discovered)
if (parameterNames != null) {
for (int i = 0; i < parameterNames.length; i++) {
setVariable(parameterNames[i], this.args[i]);
}
}
}
}
public interface KeyGenerator {
/**
* Generate a key for the given method and its parameters.
* @param target the target instance
* @param method the method being called
* @param params the method parameters (with any var-args expanded)
* @return a generated key
*/
Object generate(Object target, Method method, Object... params);
}
public class SimpleKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
return generateKey(params);
}
/**
* Generate a key based on the specified parameters.
*/
public static Object generateKey(Object... params) {
if (params.length == 0) {
return SimpleKey.EMPTY;
}
if (params.length == 1) {
Object param = params[0];
if (param != null && !param.getClass().isArray()) {
return param;
}
}
return new SimpleKey(params);
}
}
class VariableNotAvailableException extends EvaluationException {
private final String name;
public VariableNotAvailableException(String name) {
super("Variable '" + name + "' is not available");
this.name = name;
}
public String getName() {
return name;
}
}
public class SimpleKey implements Serializable {
public static final SimpleKey EMPTY = new SimpleKey();
private final Object[] params;
private final int hashCode;
/**
* Create a new {@link SimpleKey} instance.
* @param elements the elements of the key
*/
public SimpleKey(Object... elements) {
Assert.notNull(elements, "Elements must not be null");
this.params = new Object[elements.length];
System.arraycopy(elements, 0, this.params, 0, elements.length);
this.hashCode = Arrays.deepHashCode(this.params);
}
@Override
public boolean equals(Object obj) {
return (this == obj || (obj instanceof SimpleKey
&& Arrays.deepEquals(this.params, ((SimpleKey) obj).params)));
}
@Override
public final int hashCode() {
return hashCode;
}
@Override
public String toString() {
return getClass().getSimpleName() + " [" + StringUtils.arrayToCommaDelimitedString(this.params) + "]";
}
}
public final class MethodCacheKey {
private final Method method;
private final Class> targetClass;
public MethodCacheKey(Method method, Class> targetClass) {
Assert.notNull(method, "method must be set.");
this.method = method;
this.targetClass = targetClass;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof MethodCacheKey)) {
return false;
}
MethodCacheKey otherKey = (MethodCacheKey) other;
return (this.method.equals(otherKey.method) && ObjectUtils.nullSafeEquals(this.targetClass,
otherKey.targetClass));
}
@Override
public int hashCode() {
return this.method.hashCode() * 29 + (this.targetClass != null ? this.targetClass.hashCode() : 0);
}
}
@Cacheable(key = "'getTrackingListForCompany_'+#companyId+'_'+#orderNumber+'_'+#isContainSubList", timeout = 5, serialize = true)
public List getTrackingListForCompany(String companyId, int orderNumber, boolean isContainSubList) {
return trackingListDao.getTrackingListForCompany(companyId, orderNumber, isContainSubList );
}
注:这里面注解里的参数是根据每次调用的时候动态传入的,语法详见:Spring Expression Language (SpEL)
另外,Spring在3.1以后引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如 EHCache 或者 OSCache),而是一个对缓存使用的抽象,通过在既有代码中添加少量它定义的各种 annotation,即能够达到缓存方法的返回对象的效果。在这个缓存方案里面,没有定时失效的概念,而是在满足条件的时候通过代码将缓存内容进行清除。
参考文献如下:注释驱动的Spring cache缓存介绍
Spirng3基于注解(annotation)整合ehcache 使用页面缓存、对象缓存