Spring AOP+自定义注解实现缓存

Spring AOP配置:


	
		
		
	





自定义注解Cacheable代码如下:

@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;

}

切面处理类(CacheAdvice)及其父类(CacheAdviceSupport)和辅助类(ExpressionEvaluator、CacheExpressionRootObject、CacheEvaluationContext、KeyGenerator、MethodCacheKey、SimpleKey、SimpleKeyGenerator、VariableNotAvailableException)代码如下:

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 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 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 caches;

	private final Method method;

	private final Object[] args;

	private final Object target;

	private final Class targetClass;


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

}

使用自定义注解进行缓存的类的配置:


	

TrackingListServiceImpl类里面进行缓存的方法:

@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 使用页面缓存、对象缓存

你可能感兴趣的:(专业知识)