001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.cache.annotation; 018 019import java.io.Serializable; 020import java.lang.annotation.Annotation; 021import java.lang.reflect.AnnotatedElement; 022import java.lang.reflect.Method; 023import java.util.ArrayList; 024import java.util.Collection; 025import java.util.LinkedHashSet; 026import java.util.Set; 027 028import org.springframework.cache.interceptor.CacheEvictOperation; 029import org.springframework.cache.interceptor.CacheOperation; 030import org.springframework.cache.interceptor.CachePutOperation; 031import org.springframework.cache.interceptor.CacheableOperation; 032import org.springframework.core.annotation.AnnotatedElementUtils; 033import org.springframework.core.annotation.AnnotationUtils; 034import org.springframework.lang.Nullable; 035import org.springframework.util.StringUtils; 036 037/** 038 * Strategy implementation for parsing Spring's {@link Caching}, {@link Cacheable}, 039 * {@link CacheEvict}, and {@link CachePut} annotations. 040 * 041 * @author Costin Leau 042 * @author Juergen Hoeller 043 * @author Chris Beams 044 * @author Phillip Webb 045 * @author Stephane Nicoll 046 * @author Sam Brannen 047 * @since 3.1 048 */ 049@SuppressWarnings("serial") 050public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable { 051 052 private static final Set<Class<? extends Annotation>> CACHE_OPERATION_ANNOTATIONS = new LinkedHashSet<>(8); 053 054 static { 055 CACHE_OPERATION_ANNOTATIONS.add(Cacheable.class); 056 CACHE_OPERATION_ANNOTATIONS.add(CacheEvict.class); 057 CACHE_OPERATION_ANNOTATIONS.add(CachePut.class); 058 CACHE_OPERATION_ANNOTATIONS.add(Caching.class); 059 } 060 061 062 @Override 063 public boolean isCandidateClass(Class<?> targetClass) { 064 return AnnotationUtils.isCandidateClass(targetClass, CACHE_OPERATION_ANNOTATIONS); 065 } 066 067 @Override 068 @Nullable 069 public Collection<CacheOperation> parseCacheAnnotations(Class<?> type) { 070 DefaultCacheConfig defaultConfig = new DefaultCacheConfig(type); 071 return parseCacheAnnotations(defaultConfig, type); 072 } 073 074 @Override 075 @Nullable 076 public Collection<CacheOperation> parseCacheAnnotations(Method method) { 077 DefaultCacheConfig defaultConfig = new DefaultCacheConfig(method.getDeclaringClass()); 078 return parseCacheAnnotations(defaultConfig, method); 079 } 080 081 @Nullable 082 private Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae) { 083 Collection<CacheOperation> ops = parseCacheAnnotations(cachingConfig, ae, false); 084 if (ops != null && ops.size() > 1) { 085 // More than one operation found -> local declarations override interface-declared ones... 086 Collection<CacheOperation> localOps = parseCacheAnnotations(cachingConfig, ae, true); 087 if (localOps != null) { 088 return localOps; 089 } 090 } 091 return ops; 092 } 093 094 @Nullable 095 private Collection<CacheOperation> parseCacheAnnotations( 096 DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) { 097 098 Collection<? extends Annotation> anns = (localOnly ? 099 AnnotatedElementUtils.getAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS) : 100 AnnotatedElementUtils.findAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS)); 101 if (anns.isEmpty()) { 102 return null; 103 } 104 105 final Collection<CacheOperation> ops = new ArrayList<>(1); 106 anns.stream().filter(ann -> ann instanceof Cacheable).forEach( 107 ann -> ops.add(parseCacheableAnnotation(ae, cachingConfig, (Cacheable) ann))); 108 anns.stream().filter(ann -> ann instanceof CacheEvict).forEach( 109 ann -> ops.add(parseEvictAnnotation(ae, cachingConfig, (CacheEvict) ann))); 110 anns.stream().filter(ann -> ann instanceof CachePut).forEach( 111 ann -> ops.add(parsePutAnnotation(ae, cachingConfig, (CachePut) ann))); 112 anns.stream().filter(ann -> ann instanceof Caching).forEach( 113 ann -> parseCachingAnnotation(ae, cachingConfig, (Caching) ann, ops)); 114 return ops; 115 } 116 117 private CacheableOperation parseCacheableAnnotation( 118 AnnotatedElement ae, DefaultCacheConfig defaultConfig, Cacheable cacheable) { 119 120 CacheableOperation.Builder builder = new CacheableOperation.Builder(); 121 122 builder.setName(ae.toString()); 123 builder.setCacheNames(cacheable.cacheNames()); 124 builder.setCondition(cacheable.condition()); 125 builder.setUnless(cacheable.unless()); 126 builder.setKey(cacheable.key()); 127 builder.setKeyGenerator(cacheable.keyGenerator()); 128 builder.setCacheManager(cacheable.cacheManager()); 129 builder.setCacheResolver(cacheable.cacheResolver()); 130 builder.setSync(cacheable.sync()); 131 132 defaultConfig.applyDefault(builder); 133 CacheableOperation op = builder.build(); 134 validateCacheOperation(ae, op); 135 136 return op; 137 } 138 139 private CacheEvictOperation parseEvictAnnotation( 140 AnnotatedElement ae, DefaultCacheConfig defaultConfig, CacheEvict cacheEvict) { 141 142 CacheEvictOperation.Builder builder = new CacheEvictOperation.Builder(); 143 144 builder.setName(ae.toString()); 145 builder.setCacheNames(cacheEvict.cacheNames()); 146 builder.setCondition(cacheEvict.condition()); 147 builder.setKey(cacheEvict.key()); 148 builder.setKeyGenerator(cacheEvict.keyGenerator()); 149 builder.setCacheManager(cacheEvict.cacheManager()); 150 builder.setCacheResolver(cacheEvict.cacheResolver()); 151 builder.setCacheWide(cacheEvict.allEntries()); 152 builder.setBeforeInvocation(cacheEvict.beforeInvocation()); 153 154 defaultConfig.applyDefault(builder); 155 CacheEvictOperation op = builder.build(); 156 validateCacheOperation(ae, op); 157 158 return op; 159 } 160 161 private CacheOperation parsePutAnnotation( 162 AnnotatedElement ae, DefaultCacheConfig defaultConfig, CachePut cachePut) { 163 164 CachePutOperation.Builder builder = new CachePutOperation.Builder(); 165 166 builder.setName(ae.toString()); 167 builder.setCacheNames(cachePut.cacheNames()); 168 builder.setCondition(cachePut.condition()); 169 builder.setUnless(cachePut.unless()); 170 builder.setKey(cachePut.key()); 171 builder.setKeyGenerator(cachePut.keyGenerator()); 172 builder.setCacheManager(cachePut.cacheManager()); 173 builder.setCacheResolver(cachePut.cacheResolver()); 174 175 defaultConfig.applyDefault(builder); 176 CachePutOperation op = builder.build(); 177 validateCacheOperation(ae, op); 178 179 return op; 180 } 181 182 private void parseCachingAnnotation( 183 AnnotatedElement ae, DefaultCacheConfig defaultConfig, Caching caching, Collection<CacheOperation> ops) { 184 185 Cacheable[] cacheables = caching.cacheable(); 186 for (Cacheable cacheable : cacheables) { 187 ops.add(parseCacheableAnnotation(ae, defaultConfig, cacheable)); 188 } 189 CacheEvict[] cacheEvicts = caching.evict(); 190 for (CacheEvict cacheEvict : cacheEvicts) { 191 ops.add(parseEvictAnnotation(ae, defaultConfig, cacheEvict)); 192 } 193 CachePut[] cachePuts = caching.put(); 194 for (CachePut cachePut : cachePuts) { 195 ops.add(parsePutAnnotation(ae, defaultConfig, cachePut)); 196 } 197 } 198 199 200 /** 201 * Validates the specified {@link CacheOperation}. 202 * <p>Throws an {@link IllegalStateException} if the state of the operation is 203 * invalid. As there might be multiple sources for default values, this ensure 204 * that the operation is in a proper state before being returned. 205 * @param ae the annotated element of the cache operation 206 * @param operation the {@link CacheOperation} to validate 207 */ 208 private void validateCacheOperation(AnnotatedElement ae, CacheOperation operation) { 209 if (StringUtils.hasText(operation.getKey()) && StringUtils.hasText(operation.getKeyGenerator())) { 210 throw new IllegalStateException("Invalid cache annotation configuration on '" + 211 ae.toString() + "'. Both 'key' and 'keyGenerator' attributes have been set. " + 212 "These attributes are mutually exclusive: either set the SpEL expression used to" + 213 "compute the key at runtime or set the name of the KeyGenerator bean to use."); 214 } 215 if (StringUtils.hasText(operation.getCacheManager()) && StringUtils.hasText(operation.getCacheResolver())) { 216 throw new IllegalStateException("Invalid cache annotation configuration on '" + 217 ae.toString() + "'. Both 'cacheManager' and 'cacheResolver' attributes have been set. " + 218 "These attributes are mutually exclusive: the cache manager is used to configure a" + 219 "default cache resolver if none is set. If a cache resolver is set, the cache manager" + 220 "won't be used."); 221 } 222 } 223 224 @Override 225 public boolean equals(@Nullable Object other) { 226 return (this == other || other instanceof SpringCacheAnnotationParser); 227 } 228 229 @Override 230 public int hashCode() { 231 return SpringCacheAnnotationParser.class.hashCode(); 232 } 233 234 235 /** 236 * Provides default settings for a given set of cache operations. 237 */ 238 private static class DefaultCacheConfig { 239 240 private final Class<?> target; 241 242 @Nullable 243 private String[] cacheNames; 244 245 @Nullable 246 private String keyGenerator; 247 248 @Nullable 249 private String cacheManager; 250 251 @Nullable 252 private String cacheResolver; 253 254 private boolean initialized = false; 255 256 public DefaultCacheConfig(Class<?> target) { 257 this.target = target; 258 } 259 260 /** 261 * Apply the defaults to the specified {@link CacheOperation.Builder}. 262 * @param builder the operation builder to update 263 */ 264 public void applyDefault(CacheOperation.Builder builder) { 265 if (!this.initialized) { 266 CacheConfig annotation = AnnotatedElementUtils.findMergedAnnotation(this.target, CacheConfig.class); 267 if (annotation != null) { 268 this.cacheNames = annotation.cacheNames(); 269 this.keyGenerator = annotation.keyGenerator(); 270 this.cacheManager = annotation.cacheManager(); 271 this.cacheResolver = annotation.cacheResolver(); 272 } 273 this.initialized = true; 274 } 275 276 if (builder.getCacheNames().isEmpty() && this.cacheNames != null) { 277 builder.setCacheNames(this.cacheNames); 278 } 279 if (!StringUtils.hasText(builder.getKey()) && !StringUtils.hasText(builder.getKeyGenerator()) && 280 StringUtils.hasText(this.keyGenerator)) { 281 builder.setKeyGenerator(this.keyGenerator); 282 } 283 284 if (StringUtils.hasText(builder.getCacheManager()) || StringUtils.hasText(builder.getCacheResolver())) { 285 // One of these is set so we should not inherit anything 286 } 287 else if (StringUtils.hasText(this.cacheResolver)) { 288 builder.setCacheResolver(this.cacheResolver); 289 } 290 else if (StringUtils.hasText(this.cacheManager)) { 291 builder.setCacheManager(this.cacheManager); 292 } 293 } 294 } 295 296}