1 /* 2 * Copyright 2002-2011 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.springframework.cache.interceptor; 18 19 import java.lang.reflect.Method; 20 import java.lang.reflect.Modifier; 21 import java.util.Collection; 22 import java.util.Collections; 23 import java.util.Map; 24 import java.util.concurrent.ConcurrentHashMap; 25 26 import org.apache.commons.logging.Log; 27 import org.apache.commons.logging.LogFactory; 28 import org.springframework.core.BridgeMethodResolver; 29 import org.springframework.util.ClassUtils; 30 import org.springframework.util.ObjectUtils; 31 32 /** 33 * Abstract implementation of {@link CacheOperation} that caches 34 * attributes for methods and implements a fallback policy: 1. specific 35 * target method; 2. target class; 3. declaring method; 4. declaring 36 * class/interface. 37 * 38 * <p>Defaults to using the target class's caching attribute if none is 39 * associated with the target method. Any caching attribute associated 40 * with the target method completely overrides a class caching attribute. 41 * If none found on the target class, the interface that the invoked 42 * method has been called through (in case of a JDK proxy) will be 43 * checked. 44 * 45 * <p>This implementation caches attributes by method after they are 46 * first used. If it is ever desirable to allow dynamic changing of 47 * cacheable attributes (which is very unlikely), caching could be made 48 * configurable. 49 * 50 * @author Costin Leau 51 * @since 3.1 52 */ 53 public abstract class AbstractFallbackCacheOperationSource implements CacheOperationSource { 54 55 /** 56 * Canonical value held in cache to indicate no caching attribute was 57 * found for this method and we don't need to look again. 58 */ 59 private final static Collection<CacheOperation> NULL_CACHING_ATTRIBUTE = Collections.emptyList(); 60 61 /** 62 * Logger available to subclasses. 63 * <p>As this base class is not marked Serializable, the logger will be recreated 64 * after serialization - provided that the concrete subclass is Serializable. 65 */ 66 protected final Log logger = LogFactory.getLog(getClass()); 67 68 /** 69 * Cache of CacheOperations, keyed by DefaultCacheKey (Method + target Class). 70 * <p>As this base class is not marked Serializable, the cache will be recreated 71 * after serialization - provided that the concrete subclass is Serializable. 72 */ 73 final Map<Object, Collection<CacheOperation>> attributeCache = new ConcurrentHashMap<Object, Collection<CacheOperation>>(); 74 75 76 /** 77 * Determine the caching attribute for this method invocation. 78 * <p>Defaults to the class's caching attribute if no method attribute is found. 79 * @param method the method for the current invocation (never {@code null}) 80 * @param targetClass the target class for this invocation (may be {@code null}) 81 * @return {@link CacheOperation} for this method, or {@code null} if the method 82 * is not cacheable 83 */ getCacheOperations(Method method, Class<?> targetClass)84 public Collection<CacheOperation> getCacheOperations(Method method, Class<?> targetClass) { 85 // First, see if we have a cached value. 86 Object cacheKey = getCacheKey(method, targetClass); 87 Collection<CacheOperation> cached = this.attributeCache.get(cacheKey); 88 if (cached != null) { 89 if (cached == NULL_CACHING_ATTRIBUTE) { 90 return null; 91 } 92 // Value will either be canonical value indicating there is no caching attribute, 93 // or an actual caching attribute. 94 return cached; 95 } 96 else { 97 // We need to work it out. 98 Collection<CacheOperation> cacheOps = computeCacheOperations(method, targetClass); 99 // Put it in the cache. 100 if (cacheOps == null) { 101 this.attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE); 102 } 103 else { 104 if (logger.isDebugEnabled()) { 105 logger.debug("Adding cacheable method '" + method.getName() + "' with attribute: " + cacheOps); 106 } 107 this.attributeCache.put(cacheKey, cacheOps); 108 } 109 return cacheOps; 110 } 111 } 112 113 /** 114 * Determine a cache key for the given method and target class. 115 * <p>Must not produce same key for overloaded methods. 116 * Must produce same key for different instances of the same method. 117 * @param method the method (never {@code null}) 118 * @param targetClass the target class (may be {@code null}) 119 * @return the cache key (never {@code null}) 120 */ getCacheKey(Method method, Class<?> targetClass)121 protected Object getCacheKey(Method method, Class<?> targetClass) { 122 return new DefaultCacheKey(method, targetClass); 123 } 124 computeCacheOperations(Method method, Class<?> targetClass)125 private Collection<CacheOperation> computeCacheOperations(Method method, Class<?> targetClass) { 126 // Don't allow no-public methods as required. 127 if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { 128 return null; 129 } 130 131 // The method may be on an interface, but we need attributes from the target class. 132 // If the target class is null, the method will be unchanged. 133 Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); 134 // If we are dealing with method with generic parameters, find the original method. 135 specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); 136 137 // First try is the method in the target class. 138 Collection<CacheOperation> opDef = findCacheOperations(specificMethod); 139 if (opDef != null) { 140 return opDef; 141 } 142 143 // Second try is the caching operation on the target class. 144 opDef = findCacheOperations(specificMethod.getDeclaringClass()); 145 if (opDef != null) { 146 return opDef; 147 } 148 149 if (specificMethod != method) { 150 // Fall back is to look at the original method. 151 opDef = findCacheOperations(method); 152 if (opDef != null) { 153 return opDef; 154 } 155 // Last fall back is the class of the original method. 156 return findCacheOperations(method.getDeclaringClass()); 157 } 158 return null; 159 } 160 161 162 /** 163 * Subclasses need to implement this to return the caching attribute 164 * for the given method, if any. 165 * @param method the method to retrieve the attribute for 166 * @return all caching attribute associated with this method 167 * (or {@code null} if none) 168 */ findCacheOperations(Method method)169 protected abstract Collection<CacheOperation> findCacheOperations(Method method); 170 171 /** 172 * Subclasses need to implement this to return the caching attribute 173 * for the given class, if any. 174 * @param clazz the class to retrieve the attribute for 175 * @return all caching attribute associated with this class 176 * (or {@code null} if none) 177 */ findCacheOperations(Class<?> clazz)178 protected abstract Collection<CacheOperation> findCacheOperations(Class<?> clazz); 179 180 /** 181 * Should only public methods be allowed to have caching semantics? 182 * <p>The default implementation returns {@code false}. 183 */ allowPublicMethodsOnly()184 protected boolean allowPublicMethodsOnly() { 185 return false; 186 } 187 188 189 /** 190 * Default cache key for the CacheOperation cache. 191 */ 192 private static class DefaultCacheKey { 193 194 private final Method method; 195 196 private final Class<?> targetClass; 197 DefaultCacheKey(Method method, Class<?> targetClass)198 public DefaultCacheKey(Method method, Class<?> targetClass) { 199 this.method = method; 200 this.targetClass = targetClass; 201 } 202 203 @Override equals(Object other)204 public boolean equals(Object other) { 205 if (this == other) { 206 return true; 207 } 208 if (!(other instanceof DefaultCacheKey)) { 209 return false; 210 } 211 DefaultCacheKey otherKey = (DefaultCacheKey) other; 212 return (this.method.equals(otherKey.method) && ObjectUtils.nullSafeEquals(this.targetClass, 213 otherKey.targetClass)); 214 } 215 216 @Override hashCode()217 public int hashCode() { 218 return this.method.hashCode() * 29 + (this.targetClass != null ? this.targetClass.hashCode() : 0); 219 } 220 } 221 } 222