Revised retrieval of cache strategy beans

Issue: SPR-12336
This commit is contained in:
Juergen Hoeller 2014-11-01 08:26:48 +01:00
parent cfa3d358d5
commit 0e36402bd2
7 changed files with 210 additions and 176 deletions

View File

@ -28,6 +28,7 @@ import org.springframework.cache.interceptor.AbstractCacheInvoker;
import org.springframework.cache.interceptor.CacheErrorHandler; import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheOperationInvocationContext; import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.CacheOperationInvoker; import org.springframework.cache.interceptor.CacheOperationInvoker;
import org.springframework.util.CollectionUtils;
/** /**
* A base interceptor for JSR-107 cache annotations. * A base interceptor for JSR-107 cache annotations.
@ -41,12 +42,15 @@ abstract class AbstractCacheInterceptor<O extends AbstractJCacheOperation<A>, A
protected final Log logger = LogFactory.getLog(getClass()); protected final Log logger = LogFactory.getLog(getClass());
protected AbstractCacheInterceptor(CacheErrorHandler errorHandler) { protected AbstractCacheInterceptor(CacheErrorHandler errorHandler) {
super(errorHandler); super(errorHandler);
} }
protected abstract Object invoke(CacheOperationInvocationContext<O> context,
CacheOperationInvoker invoker) throws Throwable; protected abstract Object invoke(CacheOperationInvocationContext<O> context, CacheOperationInvoker invoker)
throws Throwable;
/** /**
* Resolve the cache to use. * Resolve the cache to use.
@ -68,15 +72,15 @@ abstract class AbstractCacheInterceptor<O extends AbstractJCacheOperation<A>, A
* @return the singe element or {@code null} if the collection is empty * @return the singe element or {@code null} if the collection is empty
*/ */
static Cache extractFrom(Collection<? extends Cache> caches) { static Cache extractFrom(Collection<? extends Cache> caches) {
if (caches == null || caches.size() == 0) { if (CollectionUtils.isEmpty(caches)) {
return null; return null;
} }
else if (caches.size() == 1) { else if (caches.size() == 1) {
return caches.iterator().next(); return caches.iterator().next();
} }
else { else {
throw new IllegalStateException("Unsupported cache resolution result " throw new IllegalStateException("Unsupported cache resolution result " + caches +
+ caches + " JSR-107 only supports a single cache."); ": JSR-107 only supports a single cache.");
} }
} }

View File

@ -43,35 +43,13 @@ import org.springframework.util.StringUtils;
*/ */
public abstract class AnnotationJCacheOperationSource extends AbstractFallbackJCacheOperationSource { public abstract class AnnotationJCacheOperationSource extends AbstractFallbackJCacheOperationSource {
/**
* Locate or create an instance of the specified {@code type}.
* @param type the type of the bean to manage
* @return the required bean
*/
protected abstract <T> T getBean(Class<T> type);
/**
* Return the default {@link CacheResolver} if none is set.
*/
protected abstract CacheResolver getDefaultCacheResolver();
/**
* Return the default exception {@link CacheResolver} if none is set.
*/
protected abstract CacheResolver getDefaultExceptionCacheResolver();
/**
* Return the default {@link KeyGenerator} if none is set.
*/
protected abstract KeyGenerator getDefaultKeyGenerator();
@Override @Override
protected JCacheOperation<?> findCacheOperation(Method method, Class<?> targetType) { protected JCacheOperation<?> findCacheOperation(Method method, Class<?> targetType) {
CacheResult cacheResult = method.getAnnotation(CacheResult.class); CacheResult cacheResult = method.getAnnotation(CacheResult.class);
CachePut cachePut = method.getAnnotation(CachePut.class); CachePut cachePut = method.getAnnotation(CachePut.class);
CacheRemove cacheRemove = method.getAnnotation(CacheRemove.class); CacheRemove cacheRemove = method.getAnnotation(CacheRemove.class);
CacheRemoveAll cacheRemoveAll = method.getAnnotation(CacheRemoveAll.class); CacheRemoveAll cacheRemoveAll = method.getAnnotation(CacheRemoveAll.class);
int found = countNonNull(cacheResult, cachePut, cacheRemove, cacheRemoveAll); int found = countNonNull(cacheResult, cachePut, cacheRemove, cacheRemoveAll);
if (found == 0) { if (found == 0) {
return null; return null;
@ -79,8 +57,8 @@ public abstract class AnnotationJCacheOperationSource extends AbstractFallbackJC
if (found > 1) { if (found > 1) {
throw new IllegalStateException("More than one cache annotation found on '" + method + "'"); throw new IllegalStateException("More than one cache annotation found on '" + method + "'");
} }
CacheDefaults defaults = getCacheDefaults(method, targetType);
CacheDefaults defaults = getCacheDefaults(method, targetType);
if (cacheResult != null) { if (cacheResult != null) {
return createCacheResultOperation(method, defaults, cacheResult); return createCacheResultOperation(method, defaults, cacheResult);
} }
@ -103,9 +81,7 @@ public abstract class AnnotationJCacheOperationSource extends AbstractFallbackJC
return targetType.getAnnotation(CacheDefaults.class); return targetType.getAnnotation(CacheDefaults.class);
} }
protected CacheResultOperation createCacheResultOperation(Method method, CacheDefaults defaults, CacheResult ann) {
protected CacheResultOperation createCacheResultOperation(Method method, CacheDefaults defaults,
CacheResult ann) {
String cacheName = determineCacheName(method, defaults, ann.cacheName()); String cacheName = determineCacheName(method, defaults, ann.cacheName());
CacheResolverFactory cacheResolverFactory = CacheResolverFactory cacheResolverFactory =
determineCacheResolverFactory(defaults, ann.cacheResolverFactory()); determineCacheResolverFactory(defaults, ann.cacheResolverFactory());
@ -123,49 +99,39 @@ public abstract class AnnotationJCacheOperationSource extends AbstractFallbackJC
return new CacheResultOperation(methodDetails, cacheResolver, keyGenerator, exceptionCacheResolver); return new CacheResultOperation(methodDetails, cacheResolver, keyGenerator, exceptionCacheResolver);
} }
protected CachePutOperation createCachePutOperation(Method method, CacheDefaults defaults, protected CachePutOperation createCachePutOperation(Method method, CacheDefaults defaults, CachePut ann) {
CachePut ann) {
String cacheName = determineCacheName(method, defaults, ann.cacheName()); String cacheName = determineCacheName(method, defaults, ann.cacheName());
CacheResolverFactory cacheResolverFactory = CacheResolverFactory cacheResolverFactory =
determineCacheResolverFactory(defaults, ann.cacheResolverFactory()); determineCacheResolverFactory(defaults, ann.cacheResolverFactory());
KeyGenerator keyGenerator = determineKeyGenerator(defaults, ann.cacheKeyGenerator()); KeyGenerator keyGenerator = determineKeyGenerator(defaults, ann.cacheKeyGenerator());
CacheMethodDetails<CachePut> methodDetails = createMethodDetails(method, ann, cacheName); CacheMethodDetails<CachePut> methodDetails = createMethodDetails(method, ann, cacheName);
CacheResolver cacheResolver = getCacheResolver(cacheResolverFactory, methodDetails); CacheResolver cacheResolver = getCacheResolver(cacheResolverFactory, methodDetails);
return new CachePutOperation(methodDetails, cacheResolver, keyGenerator); return new CachePutOperation(methodDetails, cacheResolver, keyGenerator);
} }
protected CacheRemoveOperation createCacheRemoveOperation(Method method, CacheDefaults defaults, protected CacheRemoveOperation createCacheRemoveOperation(Method method, CacheDefaults defaults, CacheRemove ann) {
CacheRemove ann) {
String cacheName = determineCacheName(method, defaults, ann.cacheName()); String cacheName = determineCacheName(method, defaults, ann.cacheName());
CacheResolverFactory cacheResolverFactory = CacheResolverFactory cacheResolverFactory =
determineCacheResolverFactory(defaults, ann.cacheResolverFactory()); determineCacheResolverFactory(defaults, ann.cacheResolverFactory());
KeyGenerator keyGenerator = determineKeyGenerator(defaults, ann.cacheKeyGenerator()); KeyGenerator keyGenerator = determineKeyGenerator(defaults, ann.cacheKeyGenerator());
CacheMethodDetails<CacheRemove> methodDetails = createMethodDetails(method, ann, cacheName); CacheMethodDetails<CacheRemove> methodDetails = createMethodDetails(method, ann, cacheName);
CacheResolver cacheResolver = getCacheResolver(cacheResolverFactory, methodDetails); CacheResolver cacheResolver = getCacheResolver(cacheResolverFactory, methodDetails);
return new CacheRemoveOperation(methodDetails, cacheResolver, keyGenerator); return new CacheRemoveOperation(methodDetails, cacheResolver, keyGenerator);
} }
protected CacheRemoveAllOperation createCacheRemoveAllOperation(Method method, CacheDefaults defaults, protected CacheRemoveAllOperation createCacheRemoveAllOperation(Method method, CacheDefaults defaults, CacheRemoveAll ann) {
CacheRemoveAll ann) {
String cacheName = determineCacheName(method, defaults, ann.cacheName()); String cacheName = determineCacheName(method, defaults, ann.cacheName());
CacheResolverFactory cacheResolverFactory = CacheResolverFactory cacheResolverFactory =
determineCacheResolverFactory(defaults, ann.cacheResolverFactory()); determineCacheResolverFactory(defaults, ann.cacheResolverFactory());
CacheMethodDetails<CacheRemoveAll> methodDetails = createMethodDetails(method, ann, cacheName); CacheMethodDetails<CacheRemoveAll> methodDetails = createMethodDetails(method, ann, cacheName);
CacheResolver cacheResolver = getCacheResolver(cacheResolverFactory, methodDetails); CacheResolver cacheResolver = getCacheResolver(cacheResolverFactory, methodDetails);
return new CacheRemoveAllOperation(methodDetails, cacheResolver); return new CacheRemoveAllOperation(methodDetails, cacheResolver);
} }
private <A extends Annotation> CacheMethodDetails<A> createMethodDetails( private <A extends Annotation> CacheMethodDetails<A> createMethodDetails(Method method, A annotation, String cacheName) {
Method method, A annotation, String cacheName) {
return new DefaultCacheMethodDetails<A>(method, annotation, cacheName); return new DefaultCacheMethodDetails<A>(method, annotation, cacheName);
} }
@ -181,6 +147,7 @@ public abstract class AnnotationJCacheOperationSource extends AbstractFallbackJC
protected CacheResolver getExceptionCacheResolver(CacheResolverFactory factory, protected CacheResolver getExceptionCacheResolver(CacheResolverFactory factory,
CacheMethodDetails<CacheResult> details) { CacheMethodDetails<CacheResult> details) {
if (factory != null) { if (factory != null) {
javax.cache.annotation.CacheResolver cacheResolver = factory.getExceptionCacheResolver(details); javax.cache.annotation.CacheResolver cacheResolver = factory.getExceptionCacheResolver(details);
return new CacheResolverAdapter(cacheResolver); return new CacheResolverAdapter(cacheResolver);
@ -192,6 +159,7 @@ public abstract class AnnotationJCacheOperationSource extends AbstractFallbackJC
protected CacheResolverFactory determineCacheResolverFactory(CacheDefaults defaults, protected CacheResolverFactory determineCacheResolverFactory(CacheDefaults defaults,
Class<? extends CacheResolverFactory> candidate) { Class<? extends CacheResolverFactory> candidate) {
if (!CacheResolverFactory.class.equals(candidate)) { if (!CacheResolverFactory.class.equals(candidate)) {
return getBean(candidate); return getBean(candidate);
} }
@ -203,8 +171,7 @@ public abstract class AnnotationJCacheOperationSource extends AbstractFallbackJC
} }
} }
protected KeyGenerator determineKeyGenerator(CacheDefaults defaults, protected KeyGenerator determineKeyGenerator(CacheDefaults defaults, Class<? extends CacheKeyGenerator> candidate) {
Class<? extends CacheKeyGenerator> candidate) {
if (!CacheKeyGenerator.class.equals(candidate)) { if (!CacheKeyGenerator.class.equals(candidate)) {
return new KeyGeneratorAdapter(this, getBean(candidate)); return new KeyGeneratorAdapter(this, getBean(candidate));
} }
@ -233,28 +200,48 @@ public abstract class AnnotationJCacheOperationSource extends AbstractFallbackJC
*/ */
protected String generateDefaultCacheName(Method method) { protected String generateDefaultCacheName(Method method) {
Class<?>[] parameterTypes = method.getParameterTypes(); Class<?>[] parameterTypes = method.getParameterTypes();
List<String> parameters = new ArrayList<String>(); List<String> parameters = new ArrayList<String>(parameterTypes.length);
for (Class<?> parameterType : parameterTypes) { for (Class<?> parameterType : parameterTypes) {
parameters.add(parameterType.getName()); parameters.add(parameterType.getName());
} }
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder(method.getDeclaringClass().getName());
sb.append(method.getDeclaringClass().getName()) sb.append(".").append(method.getName());
.append(".") sb.append("(").append(StringUtils.collectionToCommaDelimitedString(parameters)).append(")");
.append(method.getName())
.append("(")
.append(StringUtils.collectionToCommaDelimitedString(parameters))
.append(")");
return sb.toString(); return sb.toString();
} }
private int countNonNull(Object... instances) { private int countNonNull(Object... instances) {
int result = 0; int result = 0;
for (Object o : instances) { for (Object instance : instances) {
if (o != null) { if (instance != null) {
result += 1; result += 1;
} }
} }
return result; return result;
} }
/**
* Locate or create an instance of the specified cache strategy {@code type}.
* @param type the type of the bean to manage
* @return the required bean
*/
protected abstract <T> T getBean(Class<T> type);
/**
* Return the default {@link CacheResolver} if none is set.
*/
protected abstract CacheResolver getDefaultCacheResolver();
/**
* Return the default exception {@link CacheResolver} if none is set.
*/
protected abstract CacheResolver getDefaultExceptionCacheResolver();
/**
* Return the default {@link KeyGenerator} if none is set.
*/
protected abstract KeyGenerator getDefaultKeyGenerator();
} }

View File

@ -16,11 +16,10 @@
package org.springframework.cache.jcache.interceptor; package org.springframework.cache.jcache.interceptor;
import java.util.Map;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.cache.CacheManager; import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.CacheResolver; import org.springframework.cache.interceptor.CacheResolver;
@ -40,7 +39,7 @@ import org.springframework.util.Assert;
* @since 4.1 * @since 4.1
*/ */
public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSource public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSource
implements InitializingBean, SmartInitializingSingleton, ApplicationContextAware { implements ApplicationContextAware, InitializingBean, SmartInitializingSingleton {
private CacheManager cacheManager; private CacheManager cacheManager;
@ -54,6 +53,7 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc
private ApplicationContext applicationContext; private ApplicationContext applicationContext;
/** /**
* Set the default {@link CacheManager} to use to lookup cache by name. Only mandatory * Set the default {@link CacheManager} to use to lookup cache by name. Only mandatory
* if the {@linkplain CacheResolver cache resolvers} have not been set. * if the {@linkplain CacheResolver cache resolvers} have not been set.
@ -104,24 +104,33 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc
this.applicationContext = applicationContext; this.applicationContext = applicationContext;
} }
@Override @Override
public void afterPropertiesSet() { public void afterPropertiesSet() {
this.adaptedKeyGenerator = new KeyGeneratorAdapter(this, this.keyGenerator); this.adaptedKeyGenerator = new KeyGeneratorAdapter(this, this.keyGenerator);
} }
@Override @Override
public void afterSingletonsInstantiated() { // Make sure those are initialized on startup public void afterSingletonsInstantiated() {
Assert.notNull(getDefaultCacheResolver(), "Cache resolver should have been initialized."); // Make sure those are initialized on startup...
Assert.notNull(getDefaultExceptionCacheResolver(), "Exception cache resolver should have been initialized."); Assert.notNull(getDefaultCacheResolver(), "Cache resolver should have been initialized");
Assert.notNull(getDefaultExceptionCacheResolver(), "Exception cache resolver should have been initialized");
} }
@Override @Override
protected <T> T getBean(Class<T> type) { protected <T> T getBean(Class<T> type) {
Map<String, T> map = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.applicationContext, type); try {
if (map.size() == 1) { return this.applicationContext.getBean(type);
return map.values().iterator().next();
} }
else { catch (NoUniqueBeanDefinitionException ex) {
throw new IllegalStateException("No unique [" + type.getName() + "] bean found in application context - " +
"mark one as primary, or declare a more specific implementation type for your cache", ex);
}
catch (NoSuchBeanDefinitionException ex) {
if (logger.isDebugEnabled()) {
logger.debug("No bean of type [" + type.getName() + "] found in application context", ex);
}
return BeanUtils.instantiateClass(type); return BeanUtils.instantiateClass(type);
} }
} }
@ -149,11 +158,16 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc
private CacheManager getCacheManager() { private CacheManager getCacheManager() {
if (this.cacheManager == null) { if (this.cacheManager == null) {
this.cacheManager = this.applicationContext.getBean(CacheManager.class); try {
if (this.cacheManager == null) { this.cacheManager = this.applicationContext.getBean(CacheManager.class);
throw new IllegalStateException("No bean of type CacheManager could be found. " + }
"Register a CacheManager bean or remove the @EnableCaching annotation " + catch (NoUniqueBeanDefinitionException ex) {
"from your configuration."); throw new IllegalStateException("No unique bean of type CacheManager found. "+
"Mark one as primary or declare a specific CacheManager to use.");
}
catch (NoSuchBeanDefinitionException ex) {
throw new IllegalStateException("No bean of type CacheManager found. Register a CacheManager "+
"bean or remove the @EnableCaching annotation from your configuration.");
} }
} }
return this.cacheManager; return this.cacheManager;

View File

@ -78,9 +78,9 @@ public class JCacheAspectSupport extends AbstractCacheInvoker implements Initial
} }
public void afterPropertiesSet() { public void afterPropertiesSet() {
Assert.state(this.cacheOperationSource != null, "The 'cacheOperationSource' property is required: " + Assert.state(getCacheOperationSource() != null, "The 'cacheOperationSource' property is required: " +
"If there are no cacheable methods, then don't use a cache aspect."); "If there are no cacheable methods, then don't use a cache aspect.");
Assert.state(this.getErrorHandler() != null, "The 'errorHandler' is required."); Assert.state(getErrorHandler() != null, "The 'errorHandler' is required");
this.cacheResultInterceptor = new CacheResultInterceptor(getErrorHandler()); this.cacheResultInterceptor = new CacheResultInterceptor(getErrorHandler());
this.cachePutInterceptor = new CachePutInterceptor(getErrorHandler()); this.cachePutInterceptor = new CachePutInterceptor(getErrorHandler());
@ -92,8 +92,7 @@ public class JCacheAspectSupport extends AbstractCacheInvoker implements Initial
protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) { protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
// check whether aspect is enabled // Check whether aspect is enabled to cope with cases where the AJ is pulled in automatically
// to cope with cases where the AJ is pulled in automatically
if (this.initialized) { if (this.initialized) {
Class<?> targetClass = getTargetClass(target); Class<?> targetClass = getTargetClass(target);
JCacheOperation<?> operation = getCacheOperationSource().getCacheOperation(method, targetClass); JCacheOperation<?> operation = getCacheOperationSource().getCacheOperation(method, targetClass);
@ -108,9 +107,9 @@ public class JCacheAspectSupport extends AbstractCacheInvoker implements Initial
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private CacheOperationInvocationContext<?> createCacheOperationInvocationContext(Object target, private CacheOperationInvocationContext<?> createCacheOperationInvocationContext(
Object[] args, Object target, Object[] args, JCacheOperation<?> operation) {
JCacheOperation<?> operation) {
return new DefaultCacheInvocationContext<Annotation>( return new DefaultCacheInvocationContext<Annotation>(
(JCacheOperation<Annotation>) operation, target, args); (JCacheOperation<Annotation>) operation, target, args);
} }
@ -124,12 +123,10 @@ public class JCacheAspectSupport extends AbstractCacheInvoker implements Initial
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Object execute(CacheOperationInvocationContext<?> context, private Object execute(CacheOperationInvocationContext<?> context, CacheOperationInvoker invoker) {
CacheOperationInvoker invoker) {
CacheOperationInvoker adapter = new CacheOperationInvokerAdapter(invoker); CacheOperationInvoker adapter = new CacheOperationInvokerAdapter(invoker);
BasicOperation operation = context.getOperation(); BasicOperation operation = context.getOperation();
if (operation instanceof CacheResultOperation) { if (operation instanceof CacheResultOperation) {
return cacheResultInterceptor.invoke( return cacheResultInterceptor.invoke(
(CacheOperationInvocationContext<CacheResultOperation>) context, adapter); (CacheOperationInvocationContext<CacheResultOperation>) context, adapter);

View File

@ -20,7 +20,6 @@ import java.lang.reflect.Method;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.cache.CacheManager; import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.CacheOperationInvoker; import org.springframework.cache.interceptor.CacheOperationInvoker;
import org.springframework.cache.interceptor.CacheResolver; import org.springframework.cache.interceptor.CacheResolver;
@ -51,11 +50,11 @@ public class JCacheInterceptorTests extends AbstractJCacheTests {
try { try {
interceptor.execute(dummyInvoker, service, m, new Object[] {"myId"}); interceptor.execute(dummyInvoker, service, m, new Object[] {"myId"});
} }
catch (IllegalStateException e) { catch (IllegalStateException ex) {
assertTrue(e.getMessage().contains("JSR-107 only supports a single cache.")); assertTrue(ex.getMessage().contains("JSR-107 only supports a single cache"));
} }
catch (Throwable t) { catch (Throwable ex) {
fail("Unexpected: " + t); fail("Unexpected: " + ex);
} }
} }
@ -71,17 +70,17 @@ public class JCacheInterceptorTests extends AbstractJCacheTests {
try { try {
interceptor.execute(dummyInvoker, service, m, new Object[] {"myId"}); interceptor.execute(dummyInvoker, service, m, new Object[] {"myId"});
} }
catch (IllegalStateException e) { catch (IllegalStateException ex) {
assertTrue(e.getMessage().contains("Cache could not have been resolved for")); assertTrue(ex.getMessage().contains("Cache could not have been resolved for"));
} }
catch (Throwable t) { catch (Throwable ex) {
fail("Unexpected: " + t); fail("Unexpected: " + ex);
} }
} }
@Test @Test
public void cacheManagerMandatoryIfCacheResolverNotSetSet() { public void cacheManagerMandatoryIfCacheResolverNotSet() {
thrown.expect(NoSuchBeanDefinitionException.class); thrown.expect(IllegalStateException.class);
createOperationSource(null, null, null, defaultKeyGenerator); createOperationSource(null, null, null, defaultKeyGenerator);
} }
@ -93,23 +92,21 @@ public class JCacheInterceptorTests extends AbstractJCacheTests {
@Test @Test
public void cacheResultReturnsProperType() throws Throwable { public void cacheResultReturnsProperType() throws Throwable {
JCacheInterceptor interceptor = createInterceptor(createOperationSource( JCacheInterceptor interceptor = createInterceptor(createOperationSource(
cacheManager, defaultCacheResolver, cacheManager, defaultCacheResolver, defaultExceptionCacheResolver, defaultKeyGenerator));
defaultExceptionCacheResolver, defaultKeyGenerator));
AnnotatedJCacheableService service = new AnnotatedJCacheableService(cacheManager.getCache("default")); AnnotatedJCacheableService service = new AnnotatedJCacheableService(cacheManager.getCache("default"));
Method m = ReflectionUtils.findMethod(AnnotatedJCacheableService.class, "cache", String.class); Method method = ReflectionUtils.findMethod(AnnotatedJCacheableService.class, "cache", String.class);
CacheOperationInvoker invoker = new DummyInvoker(0L); CacheOperationInvoker invoker = new DummyInvoker(0L);
Object execute = interceptor.execute(invoker, service, m, new Object[] {"myId"}); Object execute = interceptor.execute(invoker, service, method, new Object[] {"myId"});
assertNotNull("result cannot be null.", execute); assertNotNull("result cannot be null.", execute);
assertEquals("Wrong result type", Long.class, execute.getClass()); assertEquals("Wrong result type", Long.class, execute.getClass());
assertEquals("Wrong result", 0L, execute); assertEquals("Wrong result", 0L, execute);
} }
protected JCacheOperationSource createOperationSource(CacheManager cacheManager, protected JCacheOperationSource createOperationSource(CacheManager cacheManager,
CacheResolver cacheResolver, CacheResolver cacheResolver, CacheResolver exceptionCacheResolver, KeyGenerator keyGenerator) {
CacheResolver exceptionCacheResolver,
KeyGenerator keyGenerator) {
DefaultJCacheOperationSource source = new DefaultJCacheOperationSource(); DefaultJCacheOperationSource source = new DefaultJCacheOperationSource();
source.setApplicationContext(new StaticApplicationContext()); source.setApplicationContext(new StaticApplicationContext());
source.setCacheManager(cacheManager); source.setCacheManager(cacheManager);
@ -129,6 +126,7 @@ public class JCacheInterceptorTests extends AbstractJCacheTests {
return interceptor; return interceptor;
} }
private static class DummyInvoker implements CacheOperationInvoker { private static class DummyInvoker implements CacheOperationInvoker {
private final Object result; private final Object result;

View File

@ -30,6 +30,8 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.AopProxyUtils; import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils; import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
import org.springframework.cache.Cache; import org.springframework.cache.Cache;
@ -98,9 +100,8 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
/** /**
* Set one or more cache operation sources which are used to find the cache * 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 * attributes. If more than one source is provided, they will be aggregated
* {@link CompositeCacheOperationSource}. * using a {@link CompositeCacheOperationSource}.
* @param cacheOperationSources must not be {@code null}
*/ */
public void setCacheOperationSources(CacheOperationSource... cacheOperationSources) { public void setCacheOperationSources(CacheOperationSource... cacheOperationSources) {
Assert.notEmpty(cacheOperationSources, "At least 1 CacheOperationSource needs to be specified"); Assert.notEmpty(cacheOperationSources, "At least 1 CacheOperationSource needs to be specified");
@ -132,9 +133,8 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
} }
/** /**
* Set the {@link CacheManager} to use to create a default {@link CacheResolver}. Replace * Set the {@link CacheManager} to use to create a default {@link CacheResolver}.
* the current {@link CacheResolver}, if any. * Replace the current {@link CacheResolver}, if any.
*
* @see #setCacheResolver(CacheResolver) * @see #setCacheResolver(CacheResolver)
* @see SimpleCacheResolver * @see SimpleCacheResolver
*/ */
@ -151,7 +151,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
* @see SimpleCacheResolver * @see SimpleCacheResolver
*/ */
public void setCacheResolver(CacheResolver cacheResolver) { public void setCacheResolver(CacheResolver cacheResolver) {
Assert.notNull(cacheResolver); Assert.notNull(cacheResolver, "CacheResolver must not be null");
this.cacheResolver = cacheResolver; this.cacheResolver = cacheResolver;
} }
@ -159,7 +159,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
* Return the default {@link CacheResolver} that this cache aspect delegates to. * Return the default {@link CacheResolver} that this cache aspect delegates to.
*/ */
public CacheResolver getCacheResolver() { public CacheResolver getCacheResolver() {
return cacheResolver; return this.cacheResolver;
} }
@Override @Override
@ -167,28 +167,33 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
this.applicationContext = applicationContext; this.applicationContext = applicationContext;
} }
public void afterPropertiesSet() { public void afterPropertiesSet() {
Assert.state(this.cacheOperationSource != null, "The 'cacheOperationSources' property is required: " + Assert.state(getCacheOperationSource() != null, "The 'cacheOperationSources' property is required: " +
"If there are no cacheable methods, then don't use a cache aspect."); "If there are no cacheable methods, then don't use a cache aspect.");
Assert.state(this.getErrorHandler() != null, "The 'errorHandler' is required."); Assert.state(getErrorHandler() != null, "The 'errorHandler' property is required");
} }
@Override @Override
public void afterSingletonsInstantiated() { public void afterSingletonsInstantiated() {
if (getCacheResolver() == null) { // lazy initialize cache resolver if (getCacheResolver() == null) {
CacheManager cacheManager = this.applicationContext.getBean(CacheManager.class); // Lazily initialize cache resolver via default cache manager...
if (cacheManager == null) { try {
throw new IllegalStateException("No bean of type CacheManager could be found. " + setCacheManager(this.applicationContext.getBean(CacheManager.class));
"Register a CacheManager bean or remove the @EnableCaching annotation " + }
"from your configuration."); catch (NoUniqueBeanDefinitionException ex) {
throw new IllegalStateException("No CacheResolver specified, and no unique bean of type " +
"CacheManager found. Mark one as primary or declare a specific CacheManager to use.");
}
catch (NoSuchBeanDefinitionException ex) {
throw new IllegalStateException("No CacheResolver specified, and no bean of type CacheManager found. " +
"Register a CacheManager bean or remove the @EnableCaching annotation from your configuration.");
} }
setCacheManager(cacheManager);
} }
Assert.state(this.cacheResolver != null, "'cacheResolver' is required. Either set the cache resolver " +
"to use or set the cache manager to create a default cache resolver based on it.");
this.initialized = true; this.initialized = true;
} }
/** /**
* Convenience method to return a String representation of this Method * Convenience method to return a String representation of this Method
* for use in logging. Can be overridden in subclasses to provide a * for use in logging. Can be overridden in subclasses to provide a
@ -203,19 +208,20 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
return ClassUtils.getQualifiedMethodName(specificMethod); return ClassUtils.getQualifiedMethodName(specificMethod);
} }
protected Collection<? extends Cache> getCaches(CacheOperationInvocationContext<CacheOperation> context, protected Collection<? extends Cache> getCaches(
CacheResolver cacheResolver) { CacheOperationInvocationContext<CacheOperation> context, CacheResolver cacheResolver) {
Collection<? extends Cache> caches = cacheResolver.resolveCaches(context); Collection<? extends Cache> caches = cacheResolver.resolveCaches(context);
if (caches.isEmpty()) { if (caches.isEmpty()) {
throw new IllegalStateException("No cache could be resolved for '" + context.getOperation() throw new IllegalStateException("No cache could be resolved for '" +
+ "' using resolver '" + cacheResolver context.getOperation() + "' using resolver '" + cacheResolver +
+ "'. At least one cache should be provided per cache operation."); "'. At least one cache should be provided per cache operation.");
} }
return caches; return caches;
} }
protected CacheOperationContext getOperationContext(CacheOperation operation, Method method, Object[] args, protected CacheOperationContext getOperationContext(
Object target, Class<?> targetClass) { CacheOperation operation, Method method, Object[] args, Object target, Class<?> targetClass) {
CacheOperationMetadata metadata = getCacheOperationMetadata(operation, method, targetClass); CacheOperationMetadata metadata = getCacheOperationMetadata(operation, method, targetClass);
return new CacheOperationContext(metadata, args, target); return new CacheOperationContext(metadata, args, target);
@ -230,10 +236,11 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
* @param targetClass the target type * @param targetClass the target type
* @return the resolved metadata for the operation * @return the resolved metadata for the operation
*/ */
protected CacheOperationMetadata getCacheOperationMetadata(CacheOperation operation, protected CacheOperationMetadata getCacheOperationMetadata(
Method method, Class<?> targetClass) { CacheOperation operation, Method method, Class<?> targetClass) {
final CacheOperationCacheKey cacheKey = new CacheOperationCacheKey(operation, method, targetClass);
CacheOperationMetadata metadata = metadataCache.get(cacheKey); CacheOperationCacheKey cacheKey = new CacheOperationCacheKey(operation, method, targetClass);
CacheOperationMetadata metadata = this.metadataCache.get(cacheKey);
if (metadata == null) { if (metadata == null) {
KeyGenerator operationKeyGenerator; KeyGenerator operationKeyGenerator;
if (StringUtils.hasText(operation.getKeyGenerator())) { if (StringUtils.hasText(operation.getKeyGenerator())) {
@ -255,7 +262,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
} }
metadata = new CacheOperationMetadata(operation, method, targetClass, metadata = new CacheOperationMetadata(operation, method, targetClass,
operationKeyGenerator, operationCacheResolver); operationKeyGenerator, operationCacheResolver);
metadataCache.put(cacheKey, metadata); this.metadataCache.put(cacheKey, metadata);
} }
return metadata; return metadata;
} }
@ -263,7 +270,6 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
/** /**
* Return a bean with the specified name and type. Used to resolve services that * Return a bean with the specified name and type. Used to resolve services that
* are referenced by name in a {@link CacheOperation}. * are referenced by name in a {@link CacheOperation}.
*
* @param beanName the name of the bean, as defined by the operation * @param beanName the name of the bean, as defined by the operation
* @param expectedType type type for the bean * @param expectedType type type for the bean
* @return the bean matching that name * @return the bean matching that name
@ -273,15 +279,14 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
* @see CacheOperation#cacheResolver * @see CacheOperation#cacheResolver
*/ */
protected <T> T getBean(String beanName, Class<T> expectedType) { protected <T> T getBean(String beanName, Class<T> expectedType) {
return BeanFactoryAnnotationUtils.qualifiedBeanOfType( return BeanFactoryAnnotationUtils.qualifiedBeanOfType(this.applicationContext, expectedType, beanName);
applicationContext, expectedType, beanName);
} }
/** /**
* Clear the cached metadata. * Clear the cached metadata.
*/ */
protected void clearMetadataCache() { protected void clearMetadataCache() {
metadataCache.clear(); this.metadataCache.clear();
} }
protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) { protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
@ -486,8 +491,8 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
private final MultiValueMap<Class<? extends CacheOperation>, CacheOperationContext> contexts = private final MultiValueMap<Class<? extends CacheOperation>, CacheOperationContext> contexts =
new LinkedMultiValueMap<Class<? extends CacheOperation>, CacheOperationContext>(); new LinkedMultiValueMap<Class<? extends CacheOperation>, CacheOperationContext>();
public CacheOperationContexts(Collection<? extends CacheOperation> operations, public CacheOperationContexts(Collection<? extends CacheOperation> operations, Method method,
Method method, Object[] args, Object target, Class<?> targetClass) { Object[] args, Object target, Class<?> targetClass) {
for (CacheOperation operation : operations) { for (CacheOperation operation : operations) {
this.contexts.add(operation.getClass(), getOperationContext(operation, method, args, target, targetClass)); this.contexts.add(operation.getClass(), getOperationContext(operation, method, args, target, targetClass));
@ -508,14 +513,18 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
protected static class CacheOperationMetadata { protected static class CacheOperationMetadata {
private final CacheOperation operation; private final CacheOperation operation;
private final Method method; private final Method method;
private final Class<?> targetClass; private final Class<?> targetClass;
private final KeyGenerator keyGenerator; private final KeyGenerator keyGenerator;
private final CacheResolver cacheResolver; private final CacheResolver cacheResolver;
public CacheOperationMetadata(CacheOperation operation, Method method, public CacheOperationMetadata(CacheOperation operation, Method method, Class<?> targetClass,
Class<?> targetClass, KeyGenerator keyGenerator, KeyGenerator keyGenerator, CacheResolver cacheResolver) {
CacheResolver cacheResolver) {
this.operation = operation; this.operation = operation;
this.method = method; this.method = method;
this.targetClass = targetClass; this.targetClass = targetClass;
@ -524,6 +533,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
} }
} }
protected class CacheOperationContext implements CacheOperationInvocationContext<CacheOperation> { protected class CacheOperationContext implements CacheOperationInvocationContext<CacheOperation> {
private final CacheOperationMetadata metadata; private final CacheOperationMetadata metadata;
@ -536,8 +546,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
private final MethodCacheKey methodCacheKey; private final MethodCacheKey methodCacheKey;
public CacheOperationContext(CacheOperationMetadata metadata, public CacheOperationContext(CacheOperationMetadata metadata, Object[] args, Object target) {
Object[] args, Object target) {
this.metadata = metadata; this.metadata = metadata;
this.args = extractArgs(metadata.method, args); this.args = extractArgs(metadata.method, args);
this.target = target; this.target = target;
@ -547,22 +556,22 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
@Override @Override
public CacheOperation getOperation() { public CacheOperation getOperation() {
return metadata.operation; return this.metadata.operation;
} }
@Override @Override
public Object getTarget() { public Object getTarget() {
return target; return this.target;
} }
@Override @Override
public Method getMethod() { public Method getMethod() {
return metadata.method; return this.metadata.method;
} }
@Override @Override
public Object[] getArgs() { public Object[] getArgs() {
return args; return this.args;
} }
private Object[] extractArgs(Method method, Object[] args) { private Object[] extractArgs(Method method, Object[] args) {
@ -579,7 +588,8 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
protected boolean isConditionPassing(Object result) { protected boolean isConditionPassing(Object result) {
if (StringUtils.hasText(this.metadata.operation.getCondition())) { if (StringUtils.hasText(this.metadata.operation.getCondition())) {
EvaluationContext evaluationContext = createEvaluationContext(result); EvaluationContext evaluationContext = createEvaluationContext(result);
return evaluator.condition(this.metadata.operation.getCondition(), this.methodCacheKey, evaluationContext); return evaluator.condition(this.metadata.operation.getCondition(),
this.methodCacheKey, evaluationContext);
} }
return true; return true;
} }
@ -600,15 +610,15 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
} }
/** /**
* Computes the key for the given caching operation. * Compute the key for the given caching operation.
* @return generated key (null if none can be generated) * @return the generated key, or {@code null} if none can be generated
*/ */
protected Object generateKey(Object result) { protected Object generateKey(Object result) {
if (StringUtils.hasText(this.metadata.operation.getKey())) { if (StringUtils.hasText(this.metadata.operation.getKey())) {
EvaluationContext evaluationContext = createEvaluationContext(result); EvaluationContext evaluationContext = createEvaluationContext(result);
return evaluator.key(this.metadata.operation.getKey(), this.methodCacheKey, evaluationContext); return evaluator.key(this.metadata.operation.getKey(), this.methodCacheKey, evaluationContext);
} }
return metadata.keyGenerator.generate(this.target, this.metadata.method, this.args); return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args);
} }
private EvaluationContext createEvaluationContext(Object result) { private EvaluationContext createEvaluationContext(Object result) {
@ -642,9 +652,11 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
} }
} }
private static class CacheOperationCacheKey { private static class CacheOperationCacheKey {
private final CacheOperation cacheOperation; private final CacheOperation cacheOperation;
private final MethodCacheKey methodCacheKey; private final MethodCacheKey methodCacheKey;
private CacheOperationCacheKey(CacheOperation cacheOperation, Method method, Class<?> targetClass) { private CacheOperationCacheKey(CacheOperation cacheOperation, Method method, Class<?> targetClass) {
@ -653,20 +665,21 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
} }
@Override @Override
public boolean equals(Object o) { public boolean equals(Object other) {
if (this == o) return true; if (this == other) {
if (o == null || getClass() != o.getClass()) return false; return true;
}
CacheOperationCacheKey that = (CacheOperationCacheKey) o; if (!(other instanceof CacheOperationCacheKey)) {
return cacheOperation.equals(that.cacheOperation) return false;
&& methodCacheKey.equals(that.methodCacheKey); }
CacheOperationCacheKey otherKey = (CacheOperationCacheKey) other;
return (this.cacheOperation.equals(otherKey.cacheOperation) &&
this.methodCacheKey.equals(otherKey.methodCacheKey));
} }
@Override @Override
public int hashCode() { public int hashCode() {
int result = cacheOperation.hashCode(); return (this.cacheOperation.hashCode() * 31 + this.methodCacheKey.hashCode());
result = 31 * result + methodCacheKey.hashCode();
return result;
} }
} }
} }

View File

@ -19,8 +19,6 @@ package org.springframework.cache.config;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.cache.CacheManager; import org.springframework.cache.CacheManager;
import org.springframework.cache.CacheTestUtils; import org.springframework.cache.CacheTestUtils;
import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.CachingConfigurerSupport;
@ -75,13 +73,14 @@ public class EnableCachingTests extends AbstractAnnotationTests {
ctx.refresh(); ctx.refresh();
} }
@Test(expected=NoUniqueBeanDefinitionException.class) @Test(expected = IllegalStateException.class)
public void multipleCacheManagerBeans() throws Throwable { public void multipleCacheManagerBeans() throws Throwable {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(MultiCacheManagerConfig.class); ctx.register(MultiCacheManagerConfig.class);
try { try {
ctx.refresh(); ctx.refresh();
} catch (BeanCreationException ex) { }
catch (BeanCreationException ex) {
Throwable root = ex.getRootCause(); Throwable root = ex.getRootCause();
assertTrue(root.getMessage().contains("beans of type CacheManager")); assertTrue(root.getMessage().contains("beans of type CacheManager"));
throw root; throw root;
@ -95,26 +94,28 @@ public class EnableCachingTests extends AbstractAnnotationTests {
ctx.refresh(); // does not throw ctx.refresh(); // does not throw
} }
@Test(expected=IllegalStateException.class) @Test(expected = IllegalStateException.class)
public void multipleCachingConfigurers() throws Throwable { public void multipleCachingConfigurers() throws Throwable {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(MultiCacheManagerConfigurer.class, EnableCachingConfig.class); ctx.register(MultiCacheManagerConfigurer.class, EnableCachingConfig.class);
try { try {
ctx.refresh(); ctx.refresh();
} catch (BeanCreationException ex) { }
catch (BeanCreationException ex) {
Throwable root = ex.getRootCause(); Throwable root = ex.getRootCause();
assertTrue(root.getMessage().contains("implementations of CachingConfigurer")); assertTrue(root.getMessage().contains("implementations of CachingConfigurer"));
throw root; throw root;
} }
} }
@Test(expected=NoSuchBeanDefinitionException.class) @Test(expected = IllegalStateException.class)
public void noCacheManagerBeans() throws Throwable { public void noCacheManagerBeans() throws Throwable {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(EmptyConfig.class); ctx.register(EmptyConfig.class);
try { try {
ctx.refresh(); ctx.refresh();
} catch (BeanCreationException ex) { }
catch (BeanCreationException ex) {
Throwable root = ex.getRootCause(); Throwable root = ex.getRootCause();
assertTrue(root.getMessage().contains("No bean of type CacheManager")); assertTrue(root.getMessage().contains("No bean of type CacheManager"));
throw root; throw root;
@ -149,6 +150,7 @@ public class EnableCachingTests extends AbstractAnnotationTests {
@Configuration @Configuration
@EnableCaching @EnableCaching
static class EnableCachingConfig extends CachingConfigurerSupport { static class EnableCachingConfig extends CachingConfigurerSupport {
@Override @Override
@Bean @Bean
public CacheManager cacheManager() { public CacheManager cacheManager() {
@ -198,39 +200,56 @@ public class EnableCachingTests extends AbstractAnnotationTests {
@Configuration @Configuration
@EnableCaching @EnableCaching
static class SingleCacheManagerConfig { static class SingleCacheManagerConfig {
@Bean @Bean
public CacheManager cm1() { return new NoOpCacheManager(); } public CacheManager cm1() {
return new NoOpCacheManager();
}
} }
@Configuration @Configuration
@EnableCaching @EnableCaching
static class MultiCacheManagerConfig { static class MultiCacheManagerConfig {
@Bean @Bean
public CacheManager cm1() { return new NoOpCacheManager(); } public CacheManager cm1() {
return new NoOpCacheManager();
}
@Bean @Bean
public CacheManager cm2() { return new NoOpCacheManager(); } public CacheManager cm2() {
return new NoOpCacheManager();
}
} }
@Configuration @Configuration
@EnableCaching @EnableCaching
static class MultiCacheManagerConfigurer extends CachingConfigurerSupport { static class MultiCacheManagerConfigurer extends CachingConfigurerSupport {
@Bean @Bean
public CacheManager cm1() { return new NoOpCacheManager(); } public CacheManager cm1() {
return new NoOpCacheManager();
}
@Bean @Bean
public CacheManager cm2() { return new NoOpCacheManager(); } public CacheManager cm2() {
return new NoOpCacheManager();
}
@Override @Override
public CacheManager cacheManager() { public CacheManager cacheManager() {
return cm1(); return cm1();
} }
@Override @Override
public KeyGenerator keyGenerator() { public KeyGenerator keyGenerator() {
return null; return null;
} }
} }
@Configuration @Configuration
@EnableCaching @EnableCaching
static class EmptyConfigSupportConfig extends CachingConfigurerSupport { static class EmptyConfigSupportConfig extends CachingConfigurerSupport {
@ -241,6 +260,7 @@ public class EnableCachingTests extends AbstractAnnotationTests {
} }
} }
@Configuration @Configuration
@EnableCaching @EnableCaching
static class FullCachingConfig extends CachingConfigurerSupport { static class FullCachingConfig extends CachingConfigurerSupport {
@ -263,4 +283,5 @@ public class EnableCachingTests extends AbstractAnnotationTests {
return new NamedCacheResolver(cacheManager(), "foo"); return new NamedCacheResolver(cacheManager(), "foo");
} }
} }
} }