修复@CacheConfig 无法在子类上使用的问题

This commit is contained in:
zhouhao
2017-09-13 15:47:42 +08:00
parent dfc6dd756a
commit 0deaedf4c4
6 changed files with 732 additions and 1 deletions

View File

@@ -28,5 +28,10 @@
<artifactId>hsweb-concurrent-cache</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,22 @@
package org.hswebframework.web.cache;
import org.hswebframework.web.cache.spring.fix.FixUseSupperClassAnnotationParser;
import org.hswebframework.web.cache.spring.fix.FixUseSupperClassCacheOperationSource;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.cache.interceptor.CacheOperationSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
/**
*
* @author zhouhao
*/
@Configuration
public class FixUseSupperClassAutoConfiguration {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public CacheOperationSource cacheOperationSource() {
return new FixUseSupperClassCacheOperationSource(new FixUseSupperClassAnnotationParser());
}
}

View File

@@ -0,0 +1,296 @@
/*
* Copyright 2002-2017 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.hswebframework.web.cache.spring.fix;
import org.springframework.cache.annotation.*;
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.util.ObjectUtils;
import org.springframework.util.StringUtils;
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;
/**
* 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 FixUseSupperClassAnnotationParser implements FixUseSupperClassCacheAnnotationParser, Serializable {
@Override
public Collection<CacheOperation> parseCacheAnnotations(Class targetClass, Method method) {
DefaultCacheConfig defaultConfig = getDefaultCacheConfig(targetClass);
return parseCacheAnnotations(defaultConfig, method);
}
@Override
public Collection<CacheOperation> parseCacheAnnotations(Class<?> type) {
DefaultCacheConfig defaultConfig = getDefaultCacheConfig(type);
return parseCacheAnnotations(defaultConfig, type);
}
@Override
public Collection<CacheOperation> parseCacheAnnotations(Method method) {
DefaultCacheConfig defaultConfig = getDefaultCacheConfig(method.getDeclaringClass());
return parseCacheAnnotations(defaultConfig, method);
}
protected Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae) {
Collection<CacheOperation> ops = null;
Collection<Cacheable> cacheables = AnnotatedElementUtils.getAllMergedAnnotations(ae, Cacheable.class);
if (!cacheables.isEmpty()) {
ops = lazyInit(ops);
for (Cacheable cacheable : cacheables) {
ops.add(parseCacheableAnnotation(ae, cachingConfig, cacheable));
}
}
Collection<CacheEvict> evicts = AnnotatedElementUtils.getAllMergedAnnotations(ae, CacheEvict.class);
if (!evicts.isEmpty()) {
ops = lazyInit(ops);
for (CacheEvict evict : evicts) {
ops.add(parseEvictAnnotation(ae, cachingConfig, evict));
}
}
Collection<CachePut> puts = AnnotatedElementUtils.getAllMergedAnnotations(ae, CachePut.class);
if (!puts.isEmpty()) {
ops = lazyInit(ops);
for (CachePut put : puts) {
ops.add(parsePutAnnotation(ae, cachingConfig, put));
}
}
Collection<Caching> cachings = AnnotatedElementUtils.getAllMergedAnnotations(ae, Caching.class);
if (!cachings.isEmpty()) {
ops = lazyInit(ops);
for (Caching caching : cachings) {
Collection<CacheOperation> cachingOps = parseCachingAnnotation(ae, cachingConfig, caching);
if (cachingOps != null) {
ops.addAll(cachingOps);
}
}
}
return ops;
}
private <T extends Annotation> Collection<CacheOperation> lazyInit(Collection<CacheOperation> ops) {
return (ops != null ? ops : new ArrayList<CacheOperation>(1));
}
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;
}
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;
}
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;
}
Collection<CacheOperation> parseCachingAnnotation(AnnotatedElement ae, DefaultCacheConfig defaultConfig, Caching caching) {
Collection<CacheOperation> ops = null;
Cacheable[] cacheables = caching.cacheable();
if (!ObjectUtils.isEmpty(cacheables)) {
ops = lazyInit(ops);
for (Cacheable cacheable : cacheables) {
ops.add(parseCacheableAnnotation(ae, defaultConfig, cacheable));
}
}
CacheEvict[] cacheEvicts = caching.evict();
if (!ObjectUtils.isEmpty(cacheEvicts)) {
ops = lazyInit(ops);
for (CacheEvict cacheEvict : cacheEvicts) {
ops.add(parseEvictAnnotation(ae, defaultConfig, cacheEvict));
}
}
CachePut[] cachePuts = caching.put();
if (!ObjectUtils.isEmpty(cachePuts)) {
ops = lazyInit(ops);
for (CachePut cachePut : cachePuts) {
ops.add(parsePutAnnotation(ae, defaultConfig, cachePut));
}
}
return ops;
}
/**
* Provides the {@link DefaultCacheConfig} instance for the specified {@link Class}.
*
* @param target the class-level to handle
* @return the default config (never {@code null})
*/
DefaultCacheConfig getDefaultCacheConfig(Class<?> target) {
CacheConfig annotation = AnnotatedElementUtils.getMergedAnnotation(target, CacheConfig.class);
if (annotation != null) {
return new DefaultCacheConfig(annotation.cacheNames(), annotation.keyGenerator(),
annotation.cacheManager(), annotation.cacheResolver());
}
return new DefaultCacheConfig();
}
/**
* 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(Object other) {
return (this == other || other instanceof FixUseSupperClassAnnotationParser);
}
@Override
public int hashCode() {
return FixUseSupperClassAnnotationParser.class.hashCode();
}
/**
* Provides default settings for a given set of cache operations.
*/
static class DefaultCacheConfig {
private final String[] cacheNames;
private final String keyGenerator;
private final String cacheManager;
private final String cacheResolver;
public DefaultCacheConfig() {
this(null, null, null, null);
}
private DefaultCacheConfig(String[] cacheNames, String keyGenerator, String cacheManager, String cacheResolver) {
this.cacheNames = cacheNames;
this.keyGenerator = keyGenerator;
this.cacheManager = cacheManager;
this.cacheResolver = cacheResolver;
}
/**
* Apply the defaults to the specified {@link CacheOperation.Builder}.
*
* @param builder the operation builder to update
*/
public void applyDefault(CacheOperation.Builder builder) {
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);
}
}
}
}

View File

@@ -0,0 +1,17 @@
package org.hswebframework.web.cache.spring.fix;
import org.springframework.cache.annotation.CacheAnnotationParser;
import org.springframework.cache.interceptor.CacheOperation;
import java.lang.reflect.Method;
import java.util.Collection;
/**
* TODO 完成注释
*
* @author zhouhao
*/
public interface FixUseSupperClassCacheAnnotationParser extends CacheAnnotationParser {
Collection<CacheOperation> parseCacheAnnotations(Class targetClass, Method method);
}

View File

@@ -0,0 +1,193 @@
/*
* Copyright 2002-2016 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.hswebframework.web.cache.spring.fix;
import org.springframework.cache.annotation.CacheAnnotationParser;
import org.springframework.cache.interceptor.CacheOperation;
import org.springframework.util.Assert;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.*;
/**
* Implementation of the {@link org.springframework.cache.interceptor.CacheOperationSource
* CacheOperationSource} interface for working with caching metadata in annotation format.
* <p>
* <p>This class reads Spring's {@link org.springframework.cache.annotation.Cacheable}, {@link org.springframework.cache.annotation.CachePut} and {@link org.springframework.cache.annotation.CacheEvict}
* annotations and exposes corresponding caching operation definition to Spring's cache
* infrastructure. This class may also serve as base class for a custom
* {@code CacheOperationSource}.
*
* @author Costin Leau
* @author Juergen Hoeller
* @author Stephane Nicoll
* @since 3.1
*/
@SuppressWarnings("serial")
public class FixUseSupperClassCacheOperationSource extends FixUseSupperClassFallbackCacheOperationSource implements Serializable {
private boolean publicMethodsOnly;
private final Set<FixUseSupperClassCacheAnnotationParser> annotationParsers;
/**
* Create a default AnnotationCacheOperationSource, supporting public methods
* that carry the {@code Cacheable} and {@code CacheEvict} annotations.
*/
public FixUseSupperClassCacheOperationSource() {
this(true);
}
/**
* Create a default {@code AnnotationCacheOperationSource}, supporting public methods
* that carry the {@code Cacheable} and {@code CacheEvict} annotations.
*
* @param publicMethodsOnly whether to support only annotated public methods
* typically for use with proxy-based AOP), or protected/private methods as well
* (typically used with AspectJ class weaving)
*/
public FixUseSupperClassCacheOperationSource(boolean publicMethodsOnly) {
this.publicMethodsOnly = publicMethodsOnly;
this.annotationParsers = new LinkedHashSet<>(1);
this.annotationParsers.add(new FixUseSupperClassAnnotationParser());
}
/**
* Create a custom AnnotationCacheOperationSource.
*
* @param annotationParser the CacheAnnotationParser to use
*/
public FixUseSupperClassCacheOperationSource(FixUseSupperClassCacheAnnotationParser annotationParser) {
this.publicMethodsOnly = true;
Assert.notNull(annotationParser, "CacheAnnotationParser must not be null");
this.annotationParsers = Collections.singleton(annotationParser);
}
/**
* Create a custom AnnotationCacheOperationSource.
*
* @param annotationParsers the CacheAnnotationParser to use
*/
public FixUseSupperClassCacheOperationSource(FixUseSupperClassCacheAnnotationParser... annotationParsers) {
this.publicMethodsOnly = true;
Assert.notEmpty(annotationParsers, "At least one CacheAnnotationParser needs to be specified");
Set<FixUseSupperClassCacheAnnotationParser> parsers = new LinkedHashSet<>(annotationParsers.length);
Collections.addAll(parsers, annotationParsers);
this.annotationParsers = parsers;
}
/**
* Create a custom AnnotationCacheOperationSource.
*
* @param annotationParsers the CacheAnnotationParser to use
*/
public FixUseSupperClassCacheOperationSource(Set<FixUseSupperClassCacheAnnotationParser> annotationParsers) {
this.publicMethodsOnly = true;
Assert.notEmpty(annotationParsers, "At least one CacheAnnotationParser needs to be specified");
this.annotationParsers = annotationParsers;
}
@Override
protected Collection<CacheOperation> findCacheOperations(Class<?> targetClass, Method method) {
return determineCacheOperations(parser -> parser.parseCacheAnnotations(targetClass, method));
}
@Override
protected Collection<CacheOperation> findCacheOperations(final Class<?> clazz) {
return determineCacheOperations(parser -> parser.parseCacheAnnotations(clazz));
}
// @Override
// protected Collection<CacheOperation> findCacheOperations(final Method method) {
// return determineCacheOperations(parser -> parser.parseCacheAnnotations(method));
// }
/**
* Determine the cache operation(s) for the given {@link CacheOperationProvider}.
* <p>This implementation delegates to configured
* {@link CacheAnnotationParser}s for parsing known annotations into
* Spring's metadata attribute class.
* <p>Can be overridden to support custom annotations that carry
* caching metadata.
*
* @param provider the cache operation provider to use
* @return the configured caching operations, or {@code null} if none found
*/
protected Collection<CacheOperation> determineCacheOperations(CacheOperationProvider provider) {
Collection<CacheOperation> ops = null;
for (FixUseSupperClassCacheAnnotationParser annotationParser : this.annotationParsers) {
Collection<CacheOperation> annOps = provider.getCacheOperations(annotationParser);
if (annOps != null) {
if (ops == null) {
ops = new ArrayList<>();
}
ops.addAll(annOps);
}
}
return ops;
}
/**
* By default, only public methods can be made cacheable.
*/
@Override
protected boolean allowPublicMethodsOnly() {
return this.publicMethodsOnly;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof FixUseSupperClassCacheOperationSource)) {
return false;
}
FixUseSupperClassCacheOperationSource otherCos = (FixUseSupperClassCacheOperationSource) other;
return (this.annotationParsers.equals(otherCos.annotationParsers) &&
this.publicMethodsOnly == otherCos.publicMethodsOnly);
}
@Override
public int hashCode() {
return this.annotationParsers.hashCode();
}
public void setPublicMethodsOnly(boolean publicMethodsOnly) {
this.publicMethodsOnly = publicMethodsOnly;
}
/**
* Callback interface providing {@link CacheOperation} instance(s) based on
* a given {@link CacheAnnotationParser}.
*/
protected interface CacheOperationProvider {
/**
* Return the {@link CacheOperation} instance(s) provided by the specified parser.
*
* @param parser the parser to use
* @return the cache operations, or {@code null} if none found
*/
Collection<CacheOperation> getCacheOperations(FixUseSupperClassCacheAnnotationParser parser);
}
}

View File

@@ -0,0 +1,198 @@
/*
* Copyright 2002-2017 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.hswebframework.web.cache.spring.fix;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cache.interceptor.CacheOperation;
import org.springframework.cache.interceptor.CacheOperationSource;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.MethodClassKey;
import org.springframework.util.ClassUtils;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Abstract implementation of {@link CacheOperation} that caches attributes
* for methods and implements a fallback policy: 1. specific target method;
* 2. target class; 3. declaring method; 4. declaring class/interface.
* <p>
* <p>Defaults to using the target class's caching attribute if none is
* associated with the target method. Any caching attribute associated with
* the target method completely overrides a class caching attribute.
* If none found on the target class, the interface that the invoked method
* has been called through (in case of a JDK proxy) will be checked.
* <p>
* <p>This implementation caches attributes by method after they are first
* used. If it is ever desirable to allow dynamic changing of cacheable
* attributes (which is very unlikely), caching could be made configurable.
*
* @author Costin Leau
* @author Juergen Hoeller
* @author zhouhao
* @see org.springframework.cache.interceptor.AbstractFallbackCacheOperationSource
* @since 3.1
*/
public abstract class FixUseSupperClassFallbackCacheOperationSource implements CacheOperationSource {
/**
* Canonical value held in cache to indicate no caching attribute was
* found for this method and we don't need to look again.
*/
private final static Collection<CacheOperation> NULL_CACHING_ATTRIBUTE = Collections.emptyList();
/**
* Logger available to subclasses.
* <p>As this base class is not marked Serializable, the logger will be recreated
* after serialization - provided that the concrete subclass is Serializable.
*/
protected final Log logger = LogFactory.getLog(getClass());
/**
* Cache of CacheOperations, keyed by method on a specific target class.
* <p>As this base class is not marked Serializable, the cache will be recreated
* after serialization - provided that the concrete subclass is Serializable.
*/
private final Map<Object, Collection<CacheOperation>> attributeCache =
new ConcurrentHashMap<Object, Collection<CacheOperation>>(1024);
/**
* Determine the caching attribute for this method invocation.
* <p>Defaults to the class's caching attribute if no method attribute is found.
*
* @param method the method for the current invocation (never {@code null})
* @param targetClass the target class for this invocation (may be {@code null})
* @return {@link CacheOperation} for this method, or {@code null} if the method
* is not cacheable
*/
@Override
public Collection<CacheOperation> getCacheOperations(Method method, Class<?> targetClass) {
if (method.getDeclaringClass() == Object.class) {
return null;
}
Object cacheKey = getCacheKey(method, targetClass);
Collection<CacheOperation> cached = this.attributeCache.get(cacheKey);
if (cached != null) {
return (cached != NULL_CACHING_ATTRIBUTE ? cached : null);
} else {
Collection<CacheOperation> cacheOps = computeCacheOperations(method, targetClass);
if (cacheOps != null) {
if (logger.isDebugEnabled()) {
logger.debug("Adding cacheable method '" + method.getName() + "' with attribute: " + cacheOps);
}
this.attributeCache.put(cacheKey, cacheOps);
} else {
this.attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE);
}
return cacheOps;
}
}
/**
* Determine a cache key for the given method and target class.
* <p>Must not produce same key for overloaded methods.
* Must produce same key for different instances of the same method.
*
* @param method the method (never {@code null})
* @param targetClass the target class (may be {@code null})
* @return the cache key (never {@code null})
*/
protected Object getCacheKey(Method method, Class<?> targetClass) {
return new MethodClassKey(method, targetClass);
}
private Collection<CacheOperation> computeCacheOperations(Method method, Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
// If we are dealing with method with generic parameters, find the original method.
specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
// First try is the method in the target class.
// 解决@CacheConfig不能继承的问题
Collection<CacheOperation> opDef = findCacheOperations(targetClass, specificMethod);
if (opDef != null) {
return opDef;
}
// Second try is the caching operation on the target class.
opDef = findCacheOperations(specificMethod.getDeclaringClass());
if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
return opDef;
}
if (specificMethod != method) {
// Fallback is to look at the original method.
opDef = findCacheOperations(targetClass, method);
if (opDef != null) {
return opDef;
}
// Last fallback is the class of the original method.
opDef = findCacheOperations(method.getDeclaringClass());
if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
return opDef;
}
}
return null;
}
/**
* Subclasses need to implement this to return the caching attribute
* for the given method, if any.
*
* @param method the method to retrieve the attribute for
* @return all caching attribute associated with this method
* (or {@code null} if none)
*/
protected abstract Collection<CacheOperation> findCacheOperations(Class<?> targetClass, Method method);
/**
* Subclasses need to implement this to return the caching attribute
* for the given class, if any.
*
* @param clazz the class to retrieve the attribute for
* @return all caching attribute associated with this class
* (or {@code null} if none)
*/
protected abstract Collection<CacheOperation> findCacheOperations(Class<?> clazz);
/**
* Should only public methods be allowed to have caching semantics?
* <p>The default implementation returns {@code false}.
*/
protected boolean allowPublicMethodsOnly() {
return false;
}
}