diff --git a/org.springframework.aspects/src/main/java/org/springframework/cache/aspectj/AspectJCachingConfiguration.java b/org.springframework.aspects/src/main/java/org/springframework/cache/aspectj/AspectJCachingConfiguration.java new file mode 100644 index 0000000000..ec6bda35eb --- /dev/null +++ b/org.springframework.aspects/src/main/java/org/springframework/cache/aspectj/AspectJCachingConfiguration.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cache.aspectj; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.cache.annotation.AbstractCachingConfiguration; +import org.springframework.context.annotation.AnnotationConfigUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Role; + +/** + * {@code @Configuration} class that registers the Spring infrastructure beans necessary + * to enable AspectJ-based annotation-driven cache management. + * + * @author Chris Beams + * @since 3.1 + * @see org.springframework.cache.annotation.EnableCaching + * @see org.springframework.cache.annotation.CachingConfigurationSelector + */ +@Configuration +public class AspectJCachingConfiguration extends AbstractCachingConfiguration { + + @Bean(name=AnnotationConfigUtils.CACHE_ASPECT_BEAN_NAME) + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public AnnotationCacheAspect cacheAspect() { + AnnotationCacheAspect cacheAspect = AnnotationCacheAspect.aspectOf(); + if (this.cacheManager != null) { + cacheAspect.setCacheManager(this.cacheManager); + } + if (this.keyGenerator != null) { + cacheAspect.setKeyGenerator(this.keyGenerator); + } + return cacheAspect; + } +} diff --git a/org.springframework.context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java b/org.springframework.context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java new file mode 100644 index 0000000000..7e511b01aa --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java @@ -0,0 +1,103 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cache.annotation; + +import java.util.Collection; +import java.util.Map; + +import javax.annotation.PostConstruct; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.CacheManager; +import org.springframework.cache.interceptor.KeyGenerator; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportAware; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; + +/** + * Abstract base {@code @Configuration} class providing common structure for enabling + * Spring's annotation-driven cache management capability. + * + * @author Chris Beams + * @since 3.1 + * @see EnableCaching + */ +@Configuration +public abstract class AbstractCachingConfiguration implements ImportAware { + + /** Parsed annotation metadata for {@code @EnableCaching} on the importing class. */ + protected Map enableCaching; + protected CacheManager cacheManager; + protected KeyGenerator keyGenerator; + + @Autowired(required=false) + private Collection cacheManagerBeans; + @Autowired(required=false) + private Collection cachingConfigurers; + + public void setImportMetadata(AnnotationMetadata importMetadata) { + this.enableCaching = importMetadata.getAnnotationAttributes( + EnableCaching.class.getName(), false); + Assert.notNull(this.enableCaching, + "@EnableCaching is not present on importing class " + + 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."); + } + CachingConfigurer cachingConfigurer = cachingConfigurers.iterator().next(); + this.cacheManager = cachingConfigurer.cacheManager(); + this.keyGenerator = cachingConfigurer.keyGenerator(); + } + else if (!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."); + } + CacheManager cacheManager = cacheManagerBeans.iterator().next(); + this.cacheManager = cacheManager; + // keyGenerator remains null; will fall back to default within CacheInterceptor + } + else { + throw new IllegalStateException("No bean of type CacheManager could be found. " + + "Register a CacheManager bean or remove the @EnableCaching annotation " + + "from your configuration."); + } + } +} diff --git a/org.springframework.context/src/main/java/org/springframework/cache/annotation/CachingConfigurationSelector.java b/org.springframework.context/src/main/java/org/springframework/cache/annotation/CachingConfigurationSelector.java new file mode 100644 index 0000000000..db194c1d4f --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/cache/annotation/CachingConfigurationSelector.java @@ -0,0 +1,77 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cache.annotation; + +import java.util.Map; + +import org.springframework.aop.config.AopConfigUtils; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.AdviceMode; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportSelectorContext; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.util.Assert; + +/** + * Selects which implementation of {@link AbstractCachingConfiguration} should be used + * based on the value of {@link EnableCaching#mode} on the importing @{@link Configuration} + * class. + * + * @author Chris Beams + * @since 3.1 + * @see EnableCaching + * @see AbstractCachingConfiguration + * @see ProxyCachingConfiguration + * @see org.springframework.cache.aspectj.AspectJCachingConfiguration + */ +public class CachingConfigurationSelector implements ImportSelector { + + /** + * {@inheritDoc} + *

This implementation selects {@link ProxyCachingConfiguration} if + * {@link EnableCaching#mode()} equals {@code PROXY}, and otherwise selects + * {@link org.springframework.cache.aspectj.AspectJCachingConfiguration AspectJCacheConfiguration}. + *

If {@code #mode()} equals {@code PROXY}, an auto-proxy creator bean definition + * will also be added to the enclosing {@link BeanDefinitionRegistry} and escalated + * if necessary through the usual {@link AopConfigUtils} family of methods. + */ + public String[] selectImports(ImportSelectorContext context) { + AnnotationMetadata importingClassMetadata = context.getImportingClassMetadata(); + BeanDefinitionRegistry registry = context.getBeanDefinitionRegistry(); + + Map enableCaching = + importingClassMetadata.getAnnotationAttributes(EnableCaching.class.getName()); + Assert.notNull(enableCaching, + "@EnableCaching is not present on importing class " + + importingClassMetadata.getClassName()); + + switch ((AdviceMode) enableCaching.get("mode")) { + case PROXY: + AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry); + if ((Boolean)enableCaching.get("proxyTargetClass")) { + AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); + } + return new String[] { ProxyCachingConfiguration.class.getName() }; + case ASPECTJ: + return new String[] {"org.springframework.cache.aspectj.AspectJCachingConfiguration"}; + default: + throw new IllegalArgumentException("Unknown AdviceMode " + enableCaching.get("mode")); + } + } + +} diff --git a/org.springframework.context/src/main/java/org/springframework/cache/annotation/CachingConfigurer.java b/org.springframework.context/src/main/java/org/springframework/cache/annotation/CachingConfigurer.java new file mode 100644 index 0000000000..29ebd573c3 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/cache/annotation/CachingConfigurer.java @@ -0,0 +1,61 @@ +package org.springframework.cache.annotation; + +import org.springframework.cache.CacheManager; +import org.springframework.cache.interceptor.KeyGenerator; + +/** + * Interface to be implemented by @{@link org.springframework.context.annotation.Configuration + * Configuration} classes annotated with @{@link EnableCaching} that wish or need to + * specify explicitly the {@link CacheManager} and {@link KeyGenerator} beans to be used + * for annotation-driven cache management. + * + *

See @{@link EnableCaching} for general examples and context; see + * {@link #cacheManager()} and {@link #keyGenerator()} for detailed instructions. + * + * @author Chris Beams + * @since 3.1 + * @see EnableCaching + */ +public interface CachingConfigurer { + + /** + * Return the cache manager bean to use for annotation-driven cache management. + * Implementations must explicitly declare + * {@link org.springframework.context.annotation.Bean @Bean}, e.g. + *

+	 * @Configuration
+	 * @EnableCaching
+	 * public class AppConfig implements CachingConfigurer {
+	 *     @Bean // important!
+	 *     @Override
+	 *     public CacheManager cacheManager() {
+	 *         // configure and return CacheManager instance
+	 *     }
+	 *     // ...
+	 * }
+	 * 
+ * See @{@link EnableCaching} for more complete examples. + */ + CacheManager cacheManager(); + + /** + * Return the key generator bean to use for annotation-driven cache management. + * Implementations must explicitly declare + * {@link org.springframework.context.annotation.Bean @Bean}, e.g. + *
+	 * @Configuration
+	 * @EnableCaching
+	 * public class AppConfig implements CachingConfigurer {
+	 *     @Bean // important!
+	 *     @Override
+	 *     public KeyGenerator keyGenerator() {
+	 *         // configure and return KeyGenerator instance
+	 *     }
+	 *     // ...
+	 * }
+	 * 
+ * See @{@link EnableCaching} for more complete examples. + */ + KeyGenerator keyGenerator(); + +} diff --git a/org.springframework.context/src/main/java/org/springframework/cache/annotation/EnableCaching.java b/org.springframework.context/src/main/java/org/springframework/cache/annotation/EnableCaching.java new file mode 100644 index 0000000000..a054afa6f7 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/cache/annotation/EnableCaching.java @@ -0,0 +1,177 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cache.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.AdviceMode; +import org.springframework.context.annotation.Import; +import org.springframework.core.Ordered; + +/** + * Enables Spring's annotation-driven cache management capability, similar to + * the support found in Spring's {@code } XML namespace. To be used together + * with @{@link org.springframework.context.annotation.Configuration Configuration} + * classes as follows: + *
+ * @Configuration
+ * @EnableCaching
+ * public class AppConfig {
+ *     @Bean
+ *     public MyService myService() {
+ *         // configure and return a class having @Cacheable methods
+ *         return new MyService();
+ *     }
+ *
+ *     @Bean
+ *     public CacheManager cacheManager() {
+ *         // configure and return an implementation of Spring's CacheManager SPI
+ *         SimpleCacheManager cacheManager = new SimpleCacheManager();
+ *         cacheManager.addCaches(Arrays.asList(new ConcurrentMapCache("default")));
+ *         return cacheManager;
+ *     }
+ * }
+ * + *

For reference, the example above can be compared to the following Spring XML + * configuration: + *

+ * {@code
+ * 
+ *     
+ *     
+ *     
+ *         
+ *             
+ *                 
+ *                     
+ *                 
+ *             
+ *         
+ *     
+ * 
+ * }
+ * In both of the scenarios above, {@code @EnableCaching} and {@code + * } are responsible for registering the necessary Spring + * components that power annotation-driven cache management, such as the + * {@link org.springframework.cache.interceptor.CacheInterceptor CacheInterceptor} and the + * proxy- or AspectJ-based advice that weaves the interceptor into the call stack when + * {@link org.springframework.cache.annotation.Cacheable @Cacheable} methods are invoked. + * + *

A bean of type {@link org.springframework.cache.CacheManager CacheManager} + * must be registered, as there is no reasonable default that the framework can + * use as a convention. And whereas the {@code } element assumes + * a bean named "cacheManager", {@code @EnableCaching} searches for a cache + * manager bean by type. Therefore, naming of the cache manager bean method is + * not significant. + * + *

For those that wish to establish a more direct relationship between + * {@code @EnableCaching} and the exact cache manager bean to be used, + * the {@link CachingConfigurer} callback interface may be implemented - notice the + * {@code implements} clause and the {@code @Override}-annotated methods below: + *

+ * @Configuration
+ * @EnableCaching
+ * public class AppConfig implements CachingConfigurer {
+ *     @Bean
+ *     public MyService myService() {
+ *         // configure and return a class having @Cacheable methods
+ *         return new MyService();
+ *     }
+ *
+ *     @Bean
+ *     @Override
+ *     public CacheManager cacheManager() {
+ *         // configure and return an implementation of Spring's CacheManager SPI
+ *         SimpleCacheManager cacheManager = new SimpleCacheManager();
+ *         cacheManager.addCaches(Arrays.asList(new ConcurrentMapCache("default")));
+ *         return cacheManager;
+ *     }
+ *
+ *     @Bean
+ *     @Override
+ *     public KeyGenerator keyGenerator() {
+ *         // configure and return an implementation of Spring's KeyGenerator SPI
+ *         return new MyKeyGenerator();
+ *     }
+ * }
+ * This approach may be desirable simply because it is more explicit, or it may be + * necessary in order to distinguish between two {@code CacheManager} beans present in the + * same container. + * + *

Notice also the {@code keyGenerator} method in the example above. This allows for + * customizing the strategy for cache key generation, per Spring's {@link + * org.springframework.cache.interceptor.KeyGenerator KeyGenerator} SPI. Normally, + * {@code @EnableCaching} will configure Spring's + * {@link org.springframework.cache.interceptor.DefaultKeyGenerator DefaultKeyGenerator} + * for this purpose, but when implementing {@code CachingConfigurer}, a key generator + * must be provided explicitly. Return {@code new DefaultKeyGenerator()} from this method + * if no customization is necessary. See {@link CachingConfigurer} Javadoc for further + * details. + * + *

The {@link #mode()} attribute controls how advice is applied; if the mode is + * {@link AdviceMode#PROXY} (the default), then the other attributes such as + * {@link #proxyTargetClass()} control the behavior of the proxying. + * + *

If the {@linkplain #mode} is set to {@link AdviceMode#ASPECTJ}, then the + * {@link #proxyTargetClass()} attribute is obsolete. Note also that in this case the + * {@code spring-aspects} module JAR must be present on the classpath. + * + * @author Chris Beams + * @since 3.1 + * @see CachingConfigurer + * @see CachingConfigurationSelector + * @see ProxyCachingConfiguration + * @see org.springframework.cache.aspectj.AspectJCachingConfiguration + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(CachingConfigurationSelector.class) +public @interface EnableCaching { + + /** + * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed + * to standard Java interface-based proxies. The default is {@code false}. + * Applicable only if {@link #mode()} is set to {@link AdviceMode#PROXY}. + * + *

Note that setting this attribute to {@code true} will affect all + * Spring-managed beans requiring proxying, not just those marked with + * {@code @Cacheable}. For example, other beans marked with Spring's + * {@code @Transactional} annotation will be upgraded to subclass proxying at the same + * time. This approach has no negative impact in practice unless one is explicitly + * expecting one type of proxy vs another, e.g. in tests. + */ + boolean proxyTargetClass() default false; + + /** + * Indicate how caching advice should be applied. The default is + * {@link AdviceMode.PROXY}. + * @see AdviceMode + */ + AdviceMode mode() default AdviceMode.PROXY; + + /** + * Indicate the ordering of the execution of the caching advisor + * when multiple advices are applied at a specific joinpoint. + * The default is {@link Ordered#LOWEST_PRECEDENCE}. + */ + int order() default Ordered.LOWEST_PRECEDENCE; +} diff --git a/org.springframework.context/src/main/java/org/springframework/cache/annotation/ProxyCachingConfiguration.java b/org.springframework.context/src/main/java/org/springframework/cache/annotation/ProxyCachingConfiguration.java new file mode 100644 index 0000000000..476590beac --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/cache/annotation/ProxyCachingConfiguration.java @@ -0,0 +1,71 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cache.annotation; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.cache.interceptor.BeanFactoryCacheOperationSourceAdvisor; +import org.springframework.cache.interceptor.CacheInterceptor; +import org.springframework.cache.interceptor.CacheOperationSource; +import org.springframework.context.annotation.AnnotationConfigUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Role; + +/** + * {@code @Configuration} class that registers the Spring infrastructure beans necessary + * to enable proxy-based annotation-driven cache management. + * + * @author Chris Beams + * @since 3.1 + * @see EnableCaching + * @see CachingConfigurationSelector + */ +@Configuration +public class ProxyCachingConfiguration extends AbstractCachingConfiguration { + + @Bean(name=AnnotationConfigUtils.CACHE_ADVISOR_BEAN_NAME) + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor() { + BeanFactoryCacheOperationSourceAdvisor advisor = + new BeanFactoryCacheOperationSourceAdvisor(); + advisor.setCacheOperationSource(cacheOperationSource()); + advisor.setAdvice(cacheInterceptor()); + advisor.setOrder(((Integer)this.enableCaching.get("order"))); + return advisor; + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public CacheOperationSource cacheOperationSource() { + return new AnnotationCacheOperationSource(); + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public CacheInterceptor cacheInterceptor() { + CacheInterceptor interceptor = new CacheInterceptor(); + interceptor.setCacheOperationSources(cacheOperationSource()); + if (this.cacheManager != null) { + interceptor.setCacheManager(this.cacheManager); + } + if (this.keyGenerator != null) { + interceptor.setKeyGenerator(this.keyGenerator); + } + return interceptor; + } + +} diff --git a/org.springframework.context/src/test/java/org/springframework/cache/config/EnableCachingTests.java b/org.springframework.context/src/test/java/org/springframework/cache/config/EnableCachingTests.java new file mode 100644 index 0000000000..29114dd967 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/cache/config/EnableCachingTests.java @@ -0,0 +1,184 @@ +/* + * Copyright 2010-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cache.config; + +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; + +import junit.framework.Assert; + +import org.junit.Test; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.CachingConfigurer; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.concurrent.ConcurrentMapCache; +import org.springframework.cache.interceptor.CacheInterceptor; +import org.springframework.cache.interceptor.KeyGenerator; +import org.springframework.cache.support.NoOpCacheManager; +import org.springframework.cache.support.SimpleCacheManager; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Integration tests for @EnableCaching and its related @Configuration classes. + * + * @author Chris Beams + */ +public class EnableCachingTests extends AbstractAnnotationTests { + + /** hook into superclass suite of tests */ + @Override + protected ApplicationContext getApplicationContext() { + return new AnnotationConfigApplicationContext(EnableCachingConfig.class); + } + + @Test + public void testKeyStrategy() throws Exception { + CacheInterceptor ci = ctx.getBean(CacheInterceptor.class); + Assert.assertSame(ctx.getBean(KeyGenerator.class), ci.getKeyGenerator()); + } + + // --- local tests ------- + + @Test + public void singleCacheManagerBean() throws Throwable { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(SingleCacheManagerConfig.class); + ctx.refresh(); + } + + @Test(expected=IllegalStateException.class) + public void multipleCacheManagerBeans() throws Throwable { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(MultiCacheManagerConfig.class); + try { + ctx.refresh(); + } catch (BeanCreationException ex) { + Throwable root = ex.getRootCause(); + assertTrue(root.getMessage().contains("beans of type CacheManager")); + throw root; + } + } + + @Test + public void multipleCacheManagerBeans_implementsCachingConfigurer() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(MultiCacheManagerConfigurer.class); + ctx.refresh(); // does not throw + } + + @Test(expected=IllegalStateException.class) + public void multipleCachingConfigurers() throws Throwable { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(MultiCacheManagerConfigurer.class, EnableCachingConfig.class); + try { + ctx.refresh(); + } catch (BeanCreationException ex) { + Throwable root = ex.getRootCause(); + assertTrue(root.getMessage().contains("implementations of CachingConfigurer")); + throw root; + } + } + + @Test(expected=IllegalStateException.class) + public void noCacheManagerBeans() throws Throwable { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(EmptyConfig.class); + try { + ctx.refresh(); + } catch (BeanCreationException ex) { + Throwable root = ex.getRootCause(); + assertTrue(root.getMessage().contains("No bean of type CacheManager")); + throw root; + } + } + + + @Configuration + @EnableCaching + static class EnableCachingConfig implements CachingConfigurer { + @Bean + public CacheManager cacheManager() { + SimpleCacheManager cm = new SimpleCacheManager(); + cm.setCaches(Arrays.asList( + new ConcurrentMapCache("default"), + new ConcurrentMapCache("primary"), + new ConcurrentMapCache("secondary"))); + return cm; + } + + @Bean + public CacheableService service() { + return new DefaultCacheableService(); + } + + @Bean + public CacheableService classService() { + return new AnnotatedClassCacheableService(); + } + + @Bean + public KeyGenerator keyGenerator() { + return new SomeKeyGenerator(); + } + } + + + @Configuration + @EnableCaching + static class EmptyConfig { + } + + + @Configuration + @EnableCaching + static class SingleCacheManagerConfig { + @Bean + public CacheManager cm1() { return new NoOpCacheManager(); } + } + + + @Configuration + @EnableCaching + static class MultiCacheManagerConfig { + @Bean + public CacheManager cm1() { return new NoOpCacheManager(); } + @Bean + public CacheManager cm2() { return new NoOpCacheManager(); } + } + + + @Configuration + @EnableCaching + static class MultiCacheManagerConfigurer implements CachingConfigurer { + @Bean + public CacheManager cm1() { return new NoOpCacheManager(); } + @Bean + public CacheManager cm2() { return new NoOpCacheManager(); } + + public CacheManager cacheManager() { + return cm1(); + } + public KeyGenerator keyGenerator() { + return null; + } + } +} diff --git a/org.springframework.integration-tests/src/test/java/org/springframework/cache/annotation/EnableCachingIntegrationTests.java b/org.springframework.integration-tests/src/test/java/org/springframework/cache/annotation/EnableCachingIntegrationTests.java new file mode 100644 index 0000000000..b209a01d54 --- /dev/null +++ b/org.springframework.integration-tests/src/test/java/org/springframework/cache/annotation/EnableCachingIntegrationTests.java @@ -0,0 +1,129 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cache.annotation; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.Collections; +import java.util.List; + +import org.junit.Test; +import org.springframework.aop.Advisor; +import org.springframework.aop.framework.Advised; +import org.springframework.aop.support.AopUtils; +import org.springframework.cache.CacheManager; +import org.springframework.cache.interceptor.BeanFactoryCacheOperationSourceAdvisor; +import org.springframework.cache.support.NoOpCacheManager; +import org.springframework.context.annotation.AdviceMode; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Repository; + +/** + * Integration tests for the @EnableCaching annotation. + * + * @author Chris Beams + * @since 3.1 + */ +public class EnableCachingIntegrationTests { + + @Test + public void repositoryIsClassBasedCacheProxy() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(Config.class, ProxyTargetClassCachingConfig.class); + ctx.refresh(); + + assertCacheProxying(ctx); + assertThat(AopUtils.isCglibProxy(ctx.getBean(FooRepository.class)), is(true)); + } + + @Test + public void repositoryUsesAspectJAdviceMode() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(Config.class, AspectJCacheConfig.class); + try { + ctx.refresh(); + } catch (Exception ex) { + // this test is a bit fragile, but gets the job done, proving that an + // attempt was made to look up the AJ aspect. It's due to classpath issues + // in .integration-tests that it's not found. + assertTrue(ex.getMessage().endsWith("AspectJCachingConfiguration.class] cannot be opened because it does not exist")); + } + } + + private void assertCacheProxying(AnnotationConfigApplicationContext ctx) { + FooRepository repo = ctx.getBean(FooRepository.class); + + boolean isCacheProxy = false; + if (AopUtils.isAopProxy(repo)) { + for (Advisor advisor : ((Advised)repo).getAdvisors()) { + if (advisor instanceof BeanFactoryCacheOperationSourceAdvisor) { + isCacheProxy = true; + break; + } + } + } + assertTrue("FooRepository is not a cache proxy", isCacheProxy); + } + + + @Configuration + @EnableCaching(proxyTargetClass=true) + static class ProxyTargetClassCachingConfig { + @Bean + CacheManager mgr() { + return new NoOpCacheManager(); + } + } + + + @Configuration + static class Config { + @Bean + FooRepository fooRepository() { + return new DummyFooRepository(); + } + } + + + @Configuration + @EnableCaching(mode=AdviceMode.ASPECTJ) + static class AspectJCacheConfig { + @Bean + CacheManager cacheManager() { + return new NoOpCacheManager(); + } + } + + + interface FooRepository { + List findAll(); + } + + + @Repository + static class DummyFooRepository implements FooRepository { + + @Cacheable("primary") + public List findAll() { + return Collections.emptyList(); + } + } +}