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:
parent
8e5c77dc11
commit
5aefcc802e
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,61 +53,28 @@ 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(
|
||||
importMetadata.getAnnotationAttributes(EnableCaching.class.getName(), false));
|
||||
Assert.notNull(this.enableCaching,
|
||||
"@EnableCaching is not present on importing class " +
|
||||
importMetadata.getClassName());
|
||||
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 " +
|
||||
"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);
|
||||
@Autowired(required = false)
|
||||
void setConfigurers(Collection<C> configurers) {
|
||||
if (CollectionUtils.isEmpty(configurers)) {
|
||||
return;
|
||||
}
|
||||
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.");
|
||||
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 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.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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue