Browse Source

Prevent early bean initialization with @EnableCaching

Prior to this commmit, any configuration class holding a CacheManager
bean would be eagerly instantiated. This is because the
CacheConfiguration infrastructure requests all beans of type
CacheManager.

This commit defers the resolution of the CacheManager as late
as possible.

Issue: SPR-12336
pull/692/head
Stephane Nicoll 12 years ago
parent
commit
5aefcc802e
  1. 41
      spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/DefaultJCacheOperationSource.java
  2. 5
      spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheInterceptorTests.java
  3. 57
      spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java
  4. 22
      spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java
  5. 6
      spring-context/src/test/java/org/springframework/cache/config/EnableCachingTests.java

41
spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/DefaultJCacheOperationSource.java vendored

@ -21,6 +21,7 @@ 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.BeanFactoryUtils;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
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;
import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.cache.interceptor.KeyGenerator;
@ -39,7 +40,7 @@ import org.springframework.util.Assert;
* @since 4.1 * @since 4.1
*/ */
public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSource public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSource
implements InitializingBean, ApplicationContextAware { implements InitializingBean, SmartInitializingSingleton, ApplicationContextAware {
private CacheManager cacheManager; private CacheManager cacheManager;
@ -83,7 +84,7 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc
} }
public CacheResolver getCacheResolver() { public CacheResolver getCacheResolver() {
return this.cacheResolver; return getDefaultCacheResolver();
} }
/** /**
@ -95,7 +96,7 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc
} }
public CacheResolver getExceptionCacheResolver() { public CacheResolver getExceptionCacheResolver() {
return this.exceptionCacheResolver; return getDefaultExceptionCacheResolver();
} }
@Override @Override
@ -105,17 +106,13 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc
@Override @Override
public void afterPropertiesSet() { public void afterPropertiesSet() {
Assert.state((this.cacheResolver != null && this.exceptionCacheResolver != null)
|| this.cacheManager != null, "'cacheManager' is required if cache resolvers are not set.");
Assert.state(this.applicationContext != null, "The application context was not injected as it should.");
this.adaptedKeyGenerator = new KeyGeneratorAdapter(this, this.keyGenerator); this.adaptedKeyGenerator = new KeyGeneratorAdapter(this, this.keyGenerator);
if (this.cacheResolver == null) { }
this.cacheResolver = new SimpleCacheResolver(this.cacheManager);
} @Override
if (this.exceptionCacheResolver == null) { public void afterSingletonsInstantiated() { // Make sure those are initialized on startup
this.exceptionCacheResolver = new SimpleExceptionCacheResolver(this.cacheManager); Assert.notNull(getDefaultCacheResolver(), "Cache resolver should have been initialized.");
} Assert.notNull(getDefaultExceptionCacheResolver(), "Exception cache resolver should have been initialized.");
} }
@Override @Override
@ -131,11 +128,17 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc
@Override @Override
protected CacheResolver getDefaultCacheResolver() { protected CacheResolver getDefaultCacheResolver() {
if (this.cacheResolver == null) {
this.cacheResolver = new SimpleCacheResolver(getCacheManager());
}
return this.cacheResolver; return this.cacheResolver;
} }
@Override @Override
protected CacheResolver getDefaultExceptionCacheResolver() { protected CacheResolver getDefaultExceptionCacheResolver() {
if (this.exceptionCacheResolver == null) {
this.exceptionCacheResolver = new SimpleExceptionCacheResolver(getCacheManager());
}
return this.exceptionCacheResolver; return this.exceptionCacheResolver;
} }
@ -144,4 +147,16 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc
return this.adaptedKeyGenerator; return this.adaptedKeyGenerator;
} }
private CacheManager getCacheManager() {
if (this.cacheManager == null) {
this.cacheManager = this.applicationContext.getBean(CacheManager.class);
if (this.cacheManager == null) {
throw new IllegalStateException("No bean of type CacheManager could be found. " +
"Register a CacheManager bean or remove the @EnableCaching annotation " +
"from your configuration.");
}
}
return this.cacheManager;
}
} }

5
spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheInterceptorTests.java vendored

@ -20,6 +20,7 @@ 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;
@ -80,8 +81,7 @@ public class JCacheInterceptorTests extends AbstractJCacheTests {
@Test @Test
public void cacheManagerMandatoryIfCacheResolverNotSetSet() { public void cacheManagerMandatoryIfCacheResolverNotSetSet() {
thrown.expect(IllegalStateException.class); thrown.expect(NoSuchBeanDefinitionException.class);
thrown.expectMessage("'cacheManager' is required");
createOperationSource(null, null, null, defaultKeyGenerator); createOperationSource(null, null, null, defaultKeyGenerator);
} }
@ -117,6 +117,7 @@ public class JCacheInterceptorTests extends AbstractJCacheTests {
source.setExceptionCacheResolver(exceptionCacheResolver); source.setExceptionCacheResolver(exceptionCacheResolver);
source.setKeyGenerator(keyGenerator); source.setKeyGenerator(keyGenerator);
source.afterPropertiesSet(); source.afterPropertiesSet();
source.afterSingletonsInstantiated();
return source; return source;
} }

57
spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java vendored

@ -53,61 +53,28 @@ public abstract class AbstractCachingConfiguration<C extends CachingConfigurer>
protected CacheErrorHandler errorHandler; protected CacheErrorHandler errorHandler;
@Autowired(required=false)
private Collection<CacheManager> cacheManagerBeans;
@Autowired(required=false)
private Collection<C> cachingConfigurers;
@Override @Override
public void setImportMetadata(AnnotationMetadata importMetadata) { public void setImportMetadata(AnnotationMetadata importMetadata) {
this.enableCaching = AnnotationAttributes.fromMap( this.enableCaching = AnnotationAttributes.fromMap(
importMetadata.getAnnotationAttributes(EnableCaching.class.getName(), false)); importMetadata.getAnnotationAttributes(EnableCaching.class.getName(), false));
Assert.notNull(this.enableCaching, Assert.notNull(this.enableCaching,
"@EnableCaching is not present on importing class " + "@EnableCaching is not present on importing class " +
importMetadata.getClassName()); importMetadata.getClassName());
} }
@Autowired(required = false)
/** void setConfigurers(Collection<C> configurers) {
* Determine which {@code CacheManager} bean to use. Prefer the result of if (CollectionUtils.isEmpty(configurers)) {
* {@link CachingConfigurer#cacheManager()} over any by-type matching. If none, fall return;
* back to by-type matching on {@code CacheManager}.
* @throws IllegalArgumentException if no CacheManager can be found; if more than one
* CachingConfigurer implementation exists; if multiple CacheManager beans and no
* CachingConfigurer exists to disambiguate.
*/
@PostConstruct
protected void reconcileCacheManager() {
if (!CollectionUtils.isEmpty(cachingConfigurers)) {
int nConfigurers = cachingConfigurers.size();
if (nConfigurers > 1) {
throw new IllegalStateException(nConfigurers + " implementations of " +
"CachingConfigurer were found when only 1 was expected. " +
"Refactor the configuration such that CachingConfigurer is " +
"implemented only once or not at all.");
}
C cachingConfigurer = cachingConfigurers.iterator().next();
useCachingConfigurer(cachingConfigurer);
}
if (this.cacheManager == null && !CollectionUtils.isEmpty(cacheManagerBeans)) {
int nManagers = cacheManagerBeans.size();
if (nManagers > 1) {
throw new IllegalStateException(nManagers + " beans of type CacheManager " +
"were found when only 1 was expected. Remove all but one of the " +
"CacheManager bean definitions, or implement CachingConfigurer " +
"to make explicit which CacheManager should be used for " +
"annotation-driven cache management.");
}
this.cacheManager = cacheManagerBeans.iterator().next();
// keyGenerator remains null; will fall back to default within CacheInterceptor
} }
if (this.cacheManager == null) { if (configurers.size() > 1) {
throw new IllegalStateException("No bean of type CacheManager could be found. " + throw new IllegalStateException(configurers.size() + " implementations of " +
"Register a CacheManager bean or remove the @EnableCaching annotation " + "CachingConfigurer were found when only 1 was expected. " +
"from your configuration."); "Refactor the configuration such that CachingConfigurer is " +
"implemented only once or not at all.");
} }
C configurer = configurers.iterator().next();
useCachingConfigurer(configurer);
} }
/** /**

22
spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java vendored

@ -30,6 +30,7 @@ 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.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;
import org.springframework.cache.CacheManager; import org.springframework.cache.CacheManager;
@ -72,7 +73,7 @@ import org.springframework.util.StringUtils;
* @since 3.1 * @since 3.1
*/ */
public abstract class CacheAspectSupport extends AbstractCacheInvoker public abstract class CacheAspectSupport extends AbstractCacheInvoker
implements InitializingBean, ApplicationContextAware { implements InitializingBean, SmartInitializingSingleton, ApplicationContextAware {
protected final Log logger = LogFactory.getLog(getClass()); protected final Log logger = LogFactory.getLog(getClass());
@ -167,15 +168,26 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
} }
public void afterPropertiesSet() { public void afterPropertiesSet() {
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.");
Assert.state(this.cacheOperationSource != null, "The 'cacheOperationSources' property is required: " + Assert.state(this.cacheOperationSource != 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(this.getErrorHandler() != null, "The 'errorHandler' is required.");
Assert.state(this.applicationContext != null, "The application context was not injected as it should.");
this.initialized = true;
} }
@Override
public void afterSingletonsInstantiated() {
if (getCacheResolver() == null) { // lazy initialize cache resolver
CacheManager cacheManager = this.applicationContext.getBean(CacheManager.class);
if (cacheManager == null) {
throw new IllegalStateException("No bean of type CacheManager could be 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;
}
/** /**
* Convenience method to return a String representation of this Method * Convenience method to return a String representation of this Method

6
spring-context/src/test/java/org/springframework/cache/config/EnableCachingTests.java vendored

@ -19,6 +19,8 @@ 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;
@ -73,7 +75,7 @@ public class EnableCachingTests extends AbstractAnnotationTests {
ctx.refresh(); ctx.refresh();
} }
@Test(expected=IllegalStateException.class) @Test(expected=NoUniqueBeanDefinitionException.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);
@ -106,7 +108,7 @@ public class EnableCachingTests extends AbstractAnnotationTests {
} }
} }
@Test(expected=IllegalStateException.class) @Test(expected=NoSuchBeanDefinitionException.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);

Loading…
Cancel
Save