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
This commit is contained in:
Stephane Nicoll 2014-10-15 14:19:28 +02:00
parent 8e5c77dc11
commit 5aefcc802e
5 changed files with 64 additions and 67 deletions

View File

@ -21,6 +21,7 @@ import java.util.Map;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.KeyGenerator;
@ -39,7 +40,7 @@ import org.springframework.util.Assert;
* @since 4.1
*/
public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSource
implements InitializingBean, ApplicationContextAware {
implements InitializingBean, SmartInitializingSingleton, ApplicationContextAware {
private CacheManager cacheManager;
@ -83,7 +84,7 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc
}
public CacheResolver getCacheResolver() {
return this.cacheResolver;
return getDefaultCacheResolver();
}
/**
@ -95,7 +96,7 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc
}
public CacheResolver getExceptionCacheResolver() {
return this.exceptionCacheResolver;
return getDefaultExceptionCacheResolver();
}
@Override
@ -105,17 +106,13 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc
@Override
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);
if (this.cacheResolver == null) {
this.cacheResolver = new SimpleCacheResolver(this.cacheManager);
}
if (this.exceptionCacheResolver == null) {
this.exceptionCacheResolver = new SimpleExceptionCacheResolver(this.cacheManager);
}
@Override
public void afterSingletonsInstantiated() { // Make sure those are initialized on startup
Assert.notNull(getDefaultCacheResolver(), "Cache resolver should have been initialized.");
Assert.notNull(getDefaultExceptionCacheResolver(), "Exception cache resolver should have been initialized.");
}
@Override
@ -131,11 +128,17 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc
@Override
protected CacheResolver getDefaultCacheResolver() {
if (this.cacheResolver == null) {
this.cacheResolver = new SimpleCacheResolver(getCacheManager());
}
return this.cacheResolver;
}
@Override
protected CacheResolver getDefaultExceptionCacheResolver() {
if (this.exceptionCacheResolver == null) {
this.exceptionCacheResolver = new SimpleExceptionCacheResolver(getCacheManager());
}
return this.exceptionCacheResolver;
}
@ -144,4 +147,16 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc
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;
}
}

View File

@ -20,6 +20,7 @@ import java.lang.reflect.Method;
import org.junit.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.CacheOperationInvoker;
import org.springframework.cache.interceptor.CacheResolver;
@ -80,8 +81,7 @@ public class JCacheInterceptorTests extends AbstractJCacheTests {
@Test
public void cacheManagerMandatoryIfCacheResolverNotSetSet() {
thrown.expect(IllegalStateException.class);
thrown.expectMessage("'cacheManager' is required");
thrown.expect(NoSuchBeanDefinitionException.class);
createOperationSource(null, null, null, defaultKeyGenerator);
}
@ -117,6 +117,7 @@ public class JCacheInterceptorTests extends AbstractJCacheTests {
source.setExceptionCacheResolver(exceptionCacheResolver);
source.setKeyGenerator(keyGenerator);
source.afterPropertiesSet();
source.afterSingletonsInstantiated();
return source;
}

View File

@ -53,13 +53,6 @@ public abstract class AbstractCachingConfiguration<C extends CachingConfigurer>
protected CacheErrorHandler errorHandler;
@Autowired(required=false)
private Collection<CacheManager> cacheManagerBeans;
@Autowired(required=false)
private Collection<C> cachingConfigurers;
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
this.enableCaching = AnnotationAttributes.fromMap(
@ -69,45 +62,19 @@ public abstract class AbstractCachingConfiguration<C extends CachingConfigurer>
importMetadata.getClassName());
}
/**
* Determine which {@code CacheManager} bean to use. Prefer the result of
* {@link CachingConfigurer#cacheManager()} over any by-type matching. If none, fall
* 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 " +
@Autowired(required = false)
void setConfigurers(Collection<C> configurers) {
if (CollectionUtils.isEmpty(configurers)) {
return;
}
if (configurers.size() > 1) {
throw new IllegalStateException(configurers.size() + " 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) {
throw new IllegalStateException("No bean of type CacheManager could be found. " +
"Register a CacheManager bean or remove the @EnableCaching annotation " +
"from your configuration.");
}
C configurer = configurers.iterator().next();
useCachingConfigurer(configurer);
}
/**

View File

@ -30,6 +30,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
@ -72,7 +73,7 @@ import org.springframework.util.StringUtils;
* @since 3.1
*/
public abstract class CacheAspectSupport extends AbstractCacheInvoker
implements InitializingBean, ApplicationContextAware {
implements InitializingBean, SmartInitializingSingleton, ApplicationContextAware {
protected final Log logger = LogFactory.getLog(getClass());
@ -167,15 +168,26 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
}
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: " +
"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.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

View File

@ -19,6 +19,8 @@ package org.springframework.cache.config;
import org.junit.Test;
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.CacheTestUtils;
import org.springframework.cache.annotation.CachingConfigurerSupport;
@ -73,7 +75,7 @@ public class EnableCachingTests extends AbstractAnnotationTests {
ctx.refresh();
}
@Test(expected=IllegalStateException.class)
@Test(expected=NoUniqueBeanDefinitionException.class)
public void multipleCacheManagerBeans() throws Throwable {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
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 {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(EmptyConfig.class);