/*
 * Copyright 2002-2019 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
 *
 *      https://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.annotation;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;

import org.springframework.cache.interceptor.CacheEvictOperation;
import org.springframework.cache.interceptor.CacheOperation;
import org.springframework.cache.interceptor.CachePutOperation;
import org.springframework.cache.interceptor.CacheableOperation;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;

Strategy implementation for parsing Spring's Caching, Cacheable, CacheEvict, and CachePut annotations.
Author:Costin Leau, Juergen Hoeller, Chris Beams, Phillip Webb, Stephane Nicoll, Sam Brannen
Since:3.1
/** * Strategy implementation for parsing Spring's {@link Caching}, {@link Cacheable}, * {@link CacheEvict}, and {@link CachePut} annotations. * * @author Costin Leau * @author Juergen Hoeller * @author Chris Beams * @author Phillip Webb * @author Stephane Nicoll * @author Sam Brannen * @since 3.1 */
@SuppressWarnings("serial") public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable { private static final Set<Class<? extends Annotation>> CACHE_OPERATION_ANNOTATIONS = new LinkedHashSet<>(8); static { CACHE_OPERATION_ANNOTATIONS.add(Cacheable.class); CACHE_OPERATION_ANNOTATIONS.add(CacheEvict.class); CACHE_OPERATION_ANNOTATIONS.add(CachePut.class); CACHE_OPERATION_ANNOTATIONS.add(Caching.class); } @Override public boolean isCandidateClass(Class<?> targetClass) { return AnnotationUtils.isCandidateClass(targetClass, CACHE_OPERATION_ANNOTATIONS); } @Override @Nullable public Collection<CacheOperation> parseCacheAnnotations(Class<?> type) { DefaultCacheConfig defaultConfig = new DefaultCacheConfig(type); return parseCacheAnnotations(defaultConfig, type); } @Override @Nullable public Collection<CacheOperation> parseCacheAnnotations(Method method) { DefaultCacheConfig defaultConfig = new DefaultCacheConfig(method.getDeclaringClass()); return parseCacheAnnotations(defaultConfig, method); } @Nullable private Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae) { Collection<CacheOperation> ops = parseCacheAnnotations(cachingConfig, ae, false); if (ops != null && ops.size() > 1) { // More than one operation found -> local declarations override interface-declared ones... Collection<CacheOperation> localOps = parseCacheAnnotations(cachingConfig, ae, true); if (localOps != null) { return localOps; } } return ops; } @Nullable private Collection<CacheOperation> parseCacheAnnotations( DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) { Collection<? extends Annotation> anns = (localOnly ? AnnotatedElementUtils.getAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS) : AnnotatedElementUtils.findAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS)); if (anns.isEmpty()) { return null; } final Collection<CacheOperation> ops = new ArrayList<>(1); anns.stream().filter(ann -> ann instanceof Cacheable).forEach( ann -> ops.add(parseCacheableAnnotation(ae, cachingConfig, (Cacheable) ann))); anns.stream().filter(ann -> ann instanceof CacheEvict).forEach( ann -> ops.add(parseEvictAnnotation(ae, cachingConfig, (CacheEvict) ann))); anns.stream().filter(ann -> ann instanceof CachePut).forEach( ann -> ops.add(parsePutAnnotation(ae, cachingConfig, (CachePut) ann))); anns.stream().filter(ann -> ann instanceof Caching).forEach( ann -> parseCachingAnnotation(ae, cachingConfig, (Caching) ann, ops)); return ops; } private CacheableOperation parseCacheableAnnotation( AnnotatedElement ae, DefaultCacheConfig defaultConfig, Cacheable cacheable) { CacheableOperation.Builder builder = new CacheableOperation.Builder(); builder.setName(ae.toString()); builder.setCacheNames(cacheable.cacheNames()); builder.setCondition(cacheable.condition()); builder.setUnless(cacheable.unless()); builder.setKey(cacheable.key()); builder.setKeyGenerator(cacheable.keyGenerator()); builder.setCacheManager(cacheable.cacheManager()); builder.setCacheResolver(cacheable.cacheResolver()); builder.setSync(cacheable.sync()); defaultConfig.applyDefault(builder); CacheableOperation op = builder.build(); validateCacheOperation(ae, op); return op; } private CacheEvictOperation parseEvictAnnotation( AnnotatedElement ae, DefaultCacheConfig defaultConfig, CacheEvict cacheEvict) { CacheEvictOperation.Builder builder = new CacheEvictOperation.Builder(); builder.setName(ae.toString()); builder.setCacheNames(cacheEvict.cacheNames()); builder.setCondition(cacheEvict.condition()); builder.setKey(cacheEvict.key()); builder.setKeyGenerator(cacheEvict.keyGenerator()); builder.setCacheManager(cacheEvict.cacheManager()); builder.setCacheResolver(cacheEvict.cacheResolver()); builder.setCacheWide(cacheEvict.allEntries()); builder.setBeforeInvocation(cacheEvict.beforeInvocation()); defaultConfig.applyDefault(builder); CacheEvictOperation op = builder.build(); validateCacheOperation(ae, op); return op; } private CacheOperation parsePutAnnotation( AnnotatedElement ae, DefaultCacheConfig defaultConfig, CachePut cachePut) { CachePutOperation.Builder builder = new CachePutOperation.Builder(); builder.setName(ae.toString()); builder.setCacheNames(cachePut.cacheNames()); builder.setCondition(cachePut.condition()); builder.setUnless(cachePut.unless()); builder.setKey(cachePut.key()); builder.setKeyGenerator(cachePut.keyGenerator()); builder.setCacheManager(cachePut.cacheManager()); builder.setCacheResolver(cachePut.cacheResolver()); defaultConfig.applyDefault(builder); CachePutOperation op = builder.build(); validateCacheOperation(ae, op); return op; } private void parseCachingAnnotation( AnnotatedElement ae, DefaultCacheConfig defaultConfig, Caching caching, Collection<CacheOperation> ops) { Cacheable[] cacheables = caching.cacheable(); for (Cacheable cacheable : cacheables) { ops.add(parseCacheableAnnotation(ae, defaultConfig, cacheable)); } CacheEvict[] cacheEvicts = caching.evict(); for (CacheEvict cacheEvict : cacheEvicts) { ops.add(parseEvictAnnotation(ae, defaultConfig, cacheEvict)); } CachePut[] cachePuts = caching.put(); for (CachePut cachePut : cachePuts) { ops.add(parsePutAnnotation(ae, defaultConfig, cachePut)); } }
Validates the specified CacheOperation.

Throws an IllegalStateException if the state of the operation is invalid. As there might be multiple sources for default values, this ensure that the operation is in a proper state before being returned.

Params:
  • ae – the annotated element of the cache operation
  • operation – the CacheOperation to validate
/** * Validates the specified {@link CacheOperation}. * <p>Throws an {@link IllegalStateException} if the state of the operation is * invalid. As there might be multiple sources for default values, this ensure * that the operation is in a proper state before being returned. * @param ae the annotated element of the cache operation * @param operation the {@link CacheOperation} to validate */
private void validateCacheOperation(AnnotatedElement ae, CacheOperation operation) { if (StringUtils.hasText(operation.getKey()) && StringUtils.hasText(operation.getKeyGenerator())) { throw new IllegalStateException("Invalid cache annotation configuration on '" + ae.toString() + "'. Both 'key' and 'keyGenerator' attributes have been set. " + "These attributes are mutually exclusive: either set the SpEL expression used to" + "compute the key at runtime or set the name of the KeyGenerator bean to use."); } if (StringUtils.hasText(operation.getCacheManager()) && StringUtils.hasText(operation.getCacheResolver())) { throw new IllegalStateException("Invalid cache annotation configuration on '" + ae.toString() + "'. Both 'cacheManager' and 'cacheResolver' attributes have been set. " + "These attributes are mutually exclusive: the cache manager is used to configure a" + "default cache resolver if none is set. If a cache resolver is set, the cache manager" + "won't be used."); } } @Override public boolean equals(@Nullable Object other) { return (other instanceof SpringCacheAnnotationParser); } @Override public int hashCode() { return SpringCacheAnnotationParser.class.hashCode(); }
Provides default settings for a given set of cache operations.
/** * Provides default settings for a given set of cache operations. */
private static class DefaultCacheConfig { private final Class<?> target; @Nullable private String[] cacheNames; @Nullable private String keyGenerator; @Nullable private String cacheManager; @Nullable private String cacheResolver; private boolean initialized = false; public DefaultCacheConfig(Class<?> target) { this.target = target; }
Apply the defaults to the specified Builder.
Params:
  • builder – the operation builder to update
/** * Apply the defaults to the specified {@link CacheOperation.Builder}. * @param builder the operation builder to update */
public void applyDefault(CacheOperation.Builder builder) { if (!this.initialized) { CacheConfig annotation = AnnotatedElementUtils.findMergedAnnotation(this.target, CacheConfig.class); if (annotation != null) { this.cacheNames = annotation.cacheNames(); this.keyGenerator = annotation.keyGenerator(); this.cacheManager = annotation.cacheManager(); this.cacheResolver = annotation.cacheResolver(); } this.initialized = true; } if (builder.getCacheNames().isEmpty() && this.cacheNames != null) { builder.setCacheNames(this.cacheNames); } if (!StringUtils.hasText(builder.getKey()) && !StringUtils.hasText(builder.getKeyGenerator()) && StringUtils.hasText(this.keyGenerator)) { builder.setKeyGenerator(this.keyGenerator); } if (StringUtils.hasText(builder.getCacheManager()) || StringUtils.hasText(builder.getCacheResolver())) { // One of these is set so we should not inherit anything } else if (StringUtils.hasText(this.cacheResolver)) { builder.setCacheResolver(this.cacheResolver); } else if (StringUtils.hasText(this.cacheManager)) { builder.setCacheManager(this.cacheManager); } } } }