Java tutorial
/* * Copyright 2002-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cache.interceptor; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.aop.framework.AopProxyUtils; import org.springframework.beans.factory.InitializingBean; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.expression.EvaluationContext; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import com.yjf.common.lang.result.Result; /** * Base class for caching aspects, such as the {@link CacheInterceptor} * or an AspectJ aspect. * * <p>This enables the underlying Spring caching infrastructure to be * used easily to implement an aspect for any aspect system. * * <p>Subclasses are responsible for calling methods in this class in * the correct order. * * <p>Uses the <b>Strategy</b> design pattern. A {@link CacheManager} * implementation will perform the actual cache management, and a * {@link CacheOperationSource} is used for determining caching * operations. * * <p>A cache aspect is serializable if its {@code CacheManager} and * {@code CacheOperationSource} are serializable. * * @author Costin Leau * @author Juergen Hoeller * @author Chris Beams * @since 3.1 */ public abstract class YJFCacheAspectSupport implements InitializingBean { public interface Invoker { Object invoke(); } protected final Log logger = LogFactory.getLog(getClass()); private CacheManager cacheManager; private CacheOperationSource cacheOperationSource; private final ExpressionEvaluator evaluator = new ExpressionEvaluator(); private KeyGenerator keyGenerator = new DefaultKeyGenerator(); private boolean initialized = false; private static final String CACHEABLE = "cacheable", UPDATE = "cacheupdate", EVICT = "cacheevict"; /** * Set the CacheManager that this cache aspect should delegate to. */ public void setCacheManager(CacheManager cacheManager) { this.cacheManager = cacheManager; } /** * Return the CacheManager that this cache aspect delegates to. */ public CacheManager getCacheManager() { return this.cacheManager; } /** * Set one or more cache operation sources which are used to find the cache * attributes. If more than one source is provided, they will be aggregated using a * {@link CompositeCacheOperationSource}. * @param cacheOperationSources must not be {@code null} */ public void setCacheOperationSources(CacheOperationSource... cacheOperationSources) { Assert.notEmpty(cacheOperationSources); this.cacheOperationSource = (cacheOperationSources.length > 1 ? new CompositeCacheOperationSource(cacheOperationSources) : cacheOperationSources[0]); } /** * Return the CacheOperationSource for this cache aspect. */ public CacheOperationSource getCacheOperationSource() { return this.cacheOperationSource; } /** * Set the KeyGenerator for this cache aspect. * Default is {@link DefaultKeyGenerator}. */ public void setKeyGenerator(KeyGenerator keyGenerator) { this.keyGenerator = keyGenerator; } /** * Return the KeyGenerator for this cache aspect, */ public KeyGenerator getKeyGenerator() { return this.keyGenerator; } public void afterPropertiesSet() { if (this.cacheManager == null) { throw new IllegalStateException("'cacheManager' is required"); } if (this.cacheOperationSource == null) { throw new IllegalStateException("The 'cacheOperationSources' property is required: " + "If there are no cacheable methods, then don't use a cache aspect."); } this.initialized = true; } /** * Convenience method to return a String representation of this Method * for use in logging. Can be overridden in subclasses to provide a * different identifier for the given method. * @param method the method we're interested in * @param targetClass class the method is on * @return log message identifying this method * @see org.springframework.util.ClassUtils#getQualifiedMethodName */ protected String methodIdentification(Method method, Class<?> targetClass) { Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); return ClassUtils.getQualifiedMethodName(specificMethod); } protected Collection<Cache> getCaches(CacheOperation operation) { Set<String> cacheNames = operation.getCacheNames(); Collection<Cache> caches = new ArrayList<Cache>(cacheNames.size()); for (String cacheName : cacheNames) { Cache cache = this.cacheManager.getCache(cacheName); if (cache == null) { throw new IllegalArgumentException("Cannot find cache named [" + cacheName + "] for " + operation); } caches.add(cache); } return caches; } protected CacheOperationContext getOperationContext(CacheOperation operation, Method method, Object[] args, Object target, Class<?> targetClass) { return new CacheOperationContext(operation, method, args, target, targetClass); } protected Object execute(Invoker invoker, Object target, Method method, Object[] args) { // check whether aspect is enabled // to cope with cases where the AJ is pulled in automatically if (!this.initialized) { return invoker.invoke(); } // get backing class Class<?> targetClass = AopProxyUtils.ultimateTargetClass(target); if (targetClass == null && target != null) { targetClass = target.getClass(); } final Collection<CacheOperation> cacheOp = getCacheOperationSource().getCacheOperations(method, targetClass); // analyze caching information if (!CollectionUtils.isEmpty(cacheOp)) { Map<String, Collection<CacheOperationContext>> ops = createOperationContext(cacheOp, method, args, target, targetClass); // start with evictions inspectBeforeCacheEvicts(ops.get(EVICT)); // follow up with cacheable CacheStatus status = inspectCacheables(ops.get(CACHEABLE)); Object retVal = null; Map<CacheOperationContext, Object> updates = inspectCacheUpdates(ops.get(UPDATE)); if (status != null) { if (status.updateRequired) { updates.putAll(status.cUpdates); } // return cached object else { return status.retVal; } } retVal = invoker.invoke(); inspectAfterCacheEvicts(ops.get(EVICT)); if (!updates.isEmpty()) { //?webservice ? if (getWebServiceEnable()) { if (retVal instanceof Result) { Result result = (Result) retVal; if (result.isSuccess()) { update(updates, retVal); } } else { update(updates, retVal); } } else { update(updates, retVal); } } return retVal; } return invoker.invoke(); } public abstract boolean getWebServiceEnable(); private void inspectBeforeCacheEvicts(Collection<CacheOperationContext> evictions) { inspectCacheEvicts(evictions, true); } private void inspectAfterCacheEvicts(Collection<CacheOperationContext> evictions) { inspectCacheEvicts(evictions, false); } private void inspectCacheEvicts(Collection<CacheOperationContext> evictions, boolean beforeInvocation) { if (!evictions.isEmpty()) { boolean log = logger.isTraceEnabled(); for (CacheOperationContext context : evictions) { CacheEvictOperation evictOp = (CacheEvictOperation) context.operation; if (beforeInvocation == evictOp.isBeforeInvocation()) { if (context.isConditionPassing()) { // for each cache // lazy key initialization Object key = null; for (Cache cache : context.getCaches()) { // cache-wide flush if (evictOp.isCacheWide()) { cache.clear(); if (log) { logger.trace("Invalidating entire cache for operation " + evictOp + " on method " + context.method); } } else { // check key if (key == null) { key = context.generateKey(); } if (log) { logger.trace("Invalidating cache key " + key + " for operation " + evictOp + " on method " + context.method); } cache.evict(key); } } } else { if (log) { logger.trace("Cache condition failed on method " + context.method + " for operation " + context.operation); } } } } } } private CacheStatus inspectCacheables(Collection<CacheOperationContext> cacheables) { Map<CacheOperationContext, Object> cUpdates = new LinkedHashMap<CacheOperationContext, Object>( cacheables.size()); boolean updateRequire = false; Object retVal = null; if (!cacheables.isEmpty()) { boolean log = logger.isTraceEnabled(); boolean atLeastOnePassed = false; for (CacheOperationContext context : cacheables) { if (context.isConditionPassing()) { atLeastOnePassed = true; Object key = context.generateKey(); if (log) { logger.trace("Computed cache key " + key + " for operation " + context.operation); } if (key == null) { throw new IllegalArgumentException( "Null key returned for cache operation (maybe you are using named params on classes without debug info?) " + context.operation); } // add op/key (in case an update is discovered later on) cUpdates.put(context, key); boolean localCacheHit = false; // check whether the cache needs to be inspected or not (the method will be invoked anyway) if (!updateRequire) { for (Cache cache : context.getCaches()) { Cache.ValueWrapper wrapper = cache.get(key); if (wrapper != null) { retVal = wrapper.get(); localCacheHit = true; break; } } } if (!localCacheHit) { updateRequire = true; } } else { if (log) { logger.trace("Cache condition failed on method " + context.method + " for operation " + context.operation); } } } // return a status only if at least on cacheable matched if (atLeastOnePassed) { return new CacheStatus(cUpdates, updateRequire, retVal); } } return null; } private static class CacheStatus { // caches/key final Map<CacheOperationContext, Object> cUpdates; final boolean updateRequired; final Object retVal; CacheStatus(Map<CacheOperationContext, Object> cUpdates, boolean updateRequired, Object retVal) { this.cUpdates = cUpdates; this.updateRequired = updateRequired; this.retVal = retVal; } } private Map<CacheOperationContext, Object> inspectCacheUpdates(Collection<CacheOperationContext> updates) { Map<CacheOperationContext, Object> cUpdates = new LinkedHashMap<CacheOperationContext, Object>( updates.size()); if (!updates.isEmpty()) { boolean log = logger.isTraceEnabled(); for (CacheOperationContext context : updates) { if (context.isConditionPassing()) { Object key = context.generateKey(); if (log) { logger.trace("Computed cache key " + key + " for operation " + context.operation); } if (key == null) { throw new IllegalArgumentException( "Null key returned for cache operation (maybe you are using named params on classes without debug info?) " + context.operation); } // add op/key (in case an update is discovered later on) cUpdates.put(context, key); } else { if (log) { logger.trace("Cache condition failed on method " + context.method + " for operation " + context.operation); } } } } return cUpdates; } private void update(Map<CacheOperationContext, Object> updates, Object retVal) { for (Map.Entry<CacheOperationContext, Object> entry : updates.entrySet()) { for (Cache cache : entry.getKey().getCaches()) { cache.put(entry.getValue(), retVal); } } } private Map<String, Collection<CacheOperationContext>> createOperationContext( Collection<CacheOperation> cacheOp, Method method, Object[] args, Object target, Class<?> targetClass) { Map<String, Collection<CacheOperationContext>> map = new LinkedHashMap<String, Collection<CacheOperationContext>>( 3); Collection<CacheOperationContext> cacheables = new ArrayList<CacheOperationContext>(); Collection<CacheOperationContext> evicts = new ArrayList<CacheOperationContext>(); Collection<CacheOperationContext> updates = new ArrayList<CacheOperationContext>(); for (CacheOperation cacheOperation : cacheOp) { CacheOperationContext opContext = getOperationContext(cacheOperation, method, args, target, targetClass); if (cacheOperation instanceof CacheableOperation) { cacheables.add(opContext); } if (cacheOperation instanceof CacheEvictOperation) { evicts.add(opContext); } if (cacheOperation instanceof CachePutOperation) { updates.add(opContext); } } map.put(CACHEABLE, cacheables); map.put(EVICT, evicts); map.put(UPDATE, updates); return map; } protected class CacheOperationContext { private final CacheOperation operation; private final Collection<Cache> caches; private final Object target; private final Method method; private final Object[] args; // context passed around to avoid multiple creations private final EvaluationContext evalContext; public CacheOperationContext(CacheOperation operation, Method method, Object[] args, Object target, Class<?> targetClass) { this.operation = operation; this.caches = YJFCacheAspectSupport.this.getCaches(operation); this.target = target; this.method = method; this.args = args; this.evalContext = evaluator.createEvaluationContext(caches, method, args, target, targetClass); } protected boolean isConditionPassing() { if (StringUtils.hasText(this.operation.getCondition())) { return evaluator.condition(this.operation.getCondition(), this.method, this.evalContext); } return true; } /** * Computes the key for the given caching operation. * @return generated key (null if none can be generated) */ protected Object generateKey() { if (StringUtils.hasText(this.operation.getKey())) { return evaluator.key(this.operation.getKey(), this.method, this.evalContext); } return keyGenerator.generate(this.target, this.method, this.args); } protected Collection<Cache> getCaches() { return this.caches; } } }