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:
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)) {
|
if (configurers.size() > 1) {
|
||||||
int nManagers = cacheManagerBeans.size();
|
throw new IllegalStateException(configurers.size() + " implementations of " +
|
||||||
if (nManagers > 1) {
|
"CachingConfigurer were found when only 1 was expected. " +
|
||||||
throw new IllegalStateException(nManagers + " beans of type CacheManager " +
|
"Refactor the configuration such that CachingConfigurer is " +
|
||||||
"were found when only 1 was expected. Remove all but one of the " +
|
"implemented only once or not at all.");
|
||||||
"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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user