From f6fdffd6634369d30f59ade1adc39d778d1d7e42 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sat, 14 Jul 2018 19:29:32 +0200 Subject: [PATCH] Lazily retrieve delegate beans in AsyncConfigurer and CachingConfigurer Introduces a configure method pattern for Supplier-style configuration and a common SingletonSupplier decorator for method reference suppliers. Also declares jcache.config and jcache.interceptor for non-null conventions. Issue: SPR-17021 --- .../AsyncExecutionAspectSupport.java | 44 ++--- .../AsyncExecutionInterceptor.java | 2 +- .../SimpleAsyncUncaughtExceptionHandler.java | 8 +- .../aspectj/AspectJCachingConfiguration.java | 14 +- .../aspectj/AspectJAsyncConfiguration.java | 10 +- .../AspectJEnableCachingIsolatedTests.java | 4 +- ...tationDrivenBeanDefinitionParserTests.java | 8 +- .../config/AbstractJCacheConfiguration.java | 28 ++- .../cache/jcache/config/JCacheConfigurer.java | 4 +- .../config/JCacheConfigurerSupport.java | 4 +- .../config/ProxyJCacheConfiguration.java | 6 +- .../cache/jcache/config/package-info.java | 5 + .../interceptor/AbstractCacheInterceptor.java | 1 + ...AbstractFallbackJCacheOperationSource.java | 6 +- .../interceptor/AbstractJCacheOperation.java | 2 +- .../AbstractKeyCacheInterceptor.java | 4 +- .../AnnotationJCacheOperationSource.java | 41 +++-- ...anFactoryJCacheOperationSourceAdvisor.java | 2 + .../interceptor/CachePutInterceptor.java | 12 +- .../jcache/interceptor/CachePutOperation.java | 14 +- .../CacheRemoveAllInterceptor.java | 11 +- .../CacheRemoveEntryInterceptor.java | 9 +- .../interceptor/CacheResultInterceptor.java | 17 +- .../interceptor/CacheResultOperation.java | 8 +- .../DefaultCacheKeyInvocationContext.java | 9 +- .../DefaultJCacheOperationSource.java | 125 ++++++++------ .../interceptor/JCacheAspectSupport.java | 38 +++-- .../jcache/interceptor/JCacheInterceptor.java | 26 ++- .../interceptor/JCacheOperationSource.java | 3 +- .../JCacheOperationSourcePointcut.java | 8 +- .../interceptor/KeyGeneratorAdapter.java | 11 +- .../jcache/interceptor/package-info.java | 5 + .../AnnotationCacheOperationSourceTests.java | 7 +- .../interceptor/JCacheInterceptorTests.java | 3 +- .../AbstractCachingConfiguration.java | 20 ++- .../annotation/CachingConfigurerSupport.java | 6 +- .../annotation/ProxyCachingConfiguration.java | 16 +- ...tationDrivenCacheBeanDefinitionParser.java | 8 +- .../interceptor/AbstractCacheInvoker.java | 16 +- .../interceptor/AbstractCacheResolver.java | 10 +- .../cache/interceptor/CacheAspectSupport.java | 46 ++++- .../interceptor/SimpleCacheResolver.java | 22 +++ .../AbstractAsyncConfiguration.java | 12 +- .../annotation/AsyncAnnotationAdvisor.java | 52 +++--- .../AsyncAnnotationBeanPostProcessor.java | 72 ++++---- .../annotation/ProxyAsyncConfiguration.java | 10 +- .../cache/config/EnableCachingTests.java | 30 ++-- .../annotation/EnableAsyncTests.java | 75 ++++++++- ...tationDrivenBeanDefinitionParserTests.java | 8 +- .../util/concurrent/package-info.java | 2 +- .../util/function/SingletonSupplier.java | 159 ++++++++++++++++++ .../util/function/SupplierUtils.java | 43 +++++ .../util/function/package-info.java | 9 + 53 files changed, 785 insertions(+), 330 deletions(-) create mode 100644 spring-core/src/main/java/org/springframework/util/function/SingletonSupplier.java create mode 100644 spring-core/src/main/java/org/springframework/util/function/SupplierUtils.java create mode 100644 spring-core/src/main/java/org/springframework/util/function/package-info.java diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java index 7d49865efc..171937a61b 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -24,6 +24,7 @@ import java.util.concurrent.CompletionException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.Future; +import java.util.function.Supplier; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -41,6 +42,7 @@ import org.springframework.lang.Nullable; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; import org.springframework.util.concurrent.ListenableFuture; +import org.springframework.util.function.SingletonSupplier; /** * Base class for asynchronous method execution aspects, such as @@ -72,10 +74,9 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware { private final Map executors = new ConcurrentHashMap<>(16); - @Nullable - private volatile Executor defaultExecutor; + private SingletonSupplier defaultExecutor; - private AsyncUncaughtExceptionHandler exceptionHandler; + private SingletonSupplier exceptionHandler; @Nullable private BeanFactory beanFactory; @@ -89,7 +90,8 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware { * executor will be looked up at invocation time against the enclosing bean factory */ public AsyncExecutionAspectSupport(@Nullable Executor defaultExecutor) { - this(defaultExecutor, new SimpleAsyncUncaughtExceptionHandler()); + this.defaultExecutor = new SingletonSupplier<>(defaultExecutor, () -> getDefaultExecutor(this.beanFactory)); + this.exceptionHandler = SingletonSupplier.of(SimpleAsyncUncaughtExceptionHandler::new); } /** @@ -101,11 +103,23 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware { * @param exceptionHandler the {@link AsyncUncaughtExceptionHandler} to use */ public AsyncExecutionAspectSupport(@Nullable Executor defaultExecutor, AsyncUncaughtExceptionHandler exceptionHandler) { - this.defaultExecutor = defaultExecutor; - this.exceptionHandler = exceptionHandler; + this.defaultExecutor = new SingletonSupplier<>(defaultExecutor, () -> getDefaultExecutor(this.beanFactory)); + this.exceptionHandler = SingletonSupplier.of(exceptionHandler); } + /** + * Configure this aspect with the given executor and exception handler suppliers, + * applying the corresponding default if a supplier is not resolvable. + * @since 5.1 + */ + public void configure(@Nullable Supplier defaultExecutor, + @Nullable Supplier exceptionHandler) { + + this.defaultExecutor = new SingletonSupplier<>(defaultExecutor, () -> getDefaultExecutor(this.beanFactory)); + this.exceptionHandler = new SingletonSupplier<>(exceptionHandler, SimpleAsyncUncaughtExceptionHandler::new); + } + /** * Supply the executor to be used when executing async methods. * @param defaultExecutor the {@code Executor} (typically a Spring {@code AsyncTaskExecutor} @@ -117,7 +131,7 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware { * @see #getDefaultExecutor(BeanFactory) */ public void setExecutor(Executor defaultExecutor) { - this.defaultExecutor = defaultExecutor; + this.defaultExecutor = SingletonSupplier.of(defaultExecutor); } /** @@ -125,7 +139,7 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware { * thrown by invoking asynchronous methods with a {@code void} return type. */ public void setExceptionHandler(AsyncUncaughtExceptionHandler exceptionHandler) { - this.exceptionHandler = exceptionHandler; + this.exceptionHandler = SingletonSupplier.of(exceptionHandler); } /** @@ -155,15 +169,7 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware { targetExecutor = findQualifiedExecutor(this.beanFactory, qualifier); } else { - targetExecutor = this.defaultExecutor; - if (targetExecutor == null) { - synchronized (this.executors) { - if (this.defaultExecutor == null) { - this.defaultExecutor = getDefaultExecutor(this.beanFactory); - } - targetExecutor = this.defaultExecutor; - } - } + targetExecutor = this.defaultExecutor.get(); } if (targetExecutor == null) { return null; @@ -305,7 +311,7 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware { else { // Could not transmit the exception to the caller with default executor try { - this.exceptionHandler.handleUncaughtException(ex, method, params); + this.exceptionHandler.obtain().handleUncaughtException(ex, method, params); } catch (Throwable ex2) { logger.error("Exception handler for async method '" + method.toGenericString() + diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java index d69442e474..c458723954 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/SimpleAsyncUncaughtExceptionHandler.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/SimpleAsyncUncaughtExceptionHandler.java index dffba79e80..e77f84b435 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/SimpleAsyncUncaughtExceptionHandler.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/SimpleAsyncUncaughtExceptionHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 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. @@ -29,13 +29,13 @@ import org.apache.commons.logging.LogFactory; */ public class SimpleAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler { - private final Log logger = LogFactory.getLog(SimpleAsyncUncaughtExceptionHandler.class); + private static final Log logger = LogFactory.getLog(SimpleAsyncUncaughtExceptionHandler.class); + @Override public void handleUncaughtException(Throwable ex, Method method, Object... params) { if (logger.isErrorEnabled()) { - logger.error(String.format("Unexpected error occurred invoking async " + - "method '%s'.", method), ex); + logger.error("Unexpected error occurred invoking async method: " + method, ex); } } diff --git a/spring-aspects/src/main/java/org/springframework/cache/aspectj/AspectJCachingConfiguration.java b/spring-aspects/src/main/java/org/springframework/cache/aspectj/AspectJCachingConfiguration.java index 32e5b77a15..7a47d2e1c4 100644 --- a/spring-aspects/src/main/java/org/springframework/cache/aspectj/AspectJCachingConfiguration.java +++ b/spring-aspects/src/main/java/org/springframework/cache/aspectj/AspectJCachingConfiguration.java @@ -29,6 +29,7 @@ import org.springframework.context.annotation.Role; * * @author Chris Beams * @author Stephane Nicoll + * @author Juergen Hoeller * @since 3.1 * @see org.springframework.cache.annotation.EnableCaching * @see org.springframework.cache.annotation.CachingConfigurationSelector @@ -41,18 +42,7 @@ public class AspectJCachingConfiguration extends AbstractCachingConfiguration { @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public AnnotationCacheAspect cacheAspect() { AnnotationCacheAspect cacheAspect = AnnotationCacheAspect.aspectOf(); - if (this.cacheResolver != null) { - cacheAspect.setCacheResolver(this.cacheResolver); - } - else if (this.cacheManager != null) { - cacheAspect.setCacheManager(this.cacheManager); - } - if (this.keyGenerator != null) { - cacheAspect.setKeyGenerator(this.keyGenerator); - } - if (this.errorHandler != null) { - cacheAspect.setErrorHandler(this.errorHandler); - } + cacheAspect.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager); return cacheAspect; } diff --git a/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AspectJAsyncConfiguration.java b/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AspectJAsyncConfiguration.java index bb8128d8e1..539fcb9f25 100644 --- a/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AspectJAsyncConfiguration.java +++ b/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AspectJAsyncConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 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. @@ -30,6 +30,7 @@ import org.springframework.scheduling.config.TaskManagementConfigUtils; * * @author Chris Beams * @author Stephane Nicoll + * @author Juergen Hoeller * @since 3.1 * @see EnableAsync * @see org.springframework.scheduling.annotation.AsyncConfigurationSelector @@ -42,12 +43,7 @@ public class AspectJAsyncConfiguration extends AbstractAsyncConfiguration { @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public AnnotationAsyncExecutionAspect asyncAdvisor() { AnnotationAsyncExecutionAspect asyncAspect = AnnotationAsyncExecutionAspect.aspectOf(); - if (this.executor != null) { - asyncAspect.setExecutor(this.executor); - } - if (this.exceptionHandler != null) { - asyncAspect.setExceptionHandler(this.exceptionHandler); - } + asyncAspect.configure(this.executor, this.exceptionHandler); return asyncAspect; } diff --git a/spring-aspects/src/test/java/org/springframework/cache/aspectj/AspectJEnableCachingIsolatedTests.java b/spring-aspects/src/test/java/org/springframework/cache/aspectj/AspectJEnableCachingIsolatedTests.java index 15680c9ac6..269f607dbb 100644 --- a/spring-aspects/src/test/java/org/springframework/cache/aspectj/AspectJEnableCachingIsolatedTests.java +++ b/spring-aspects/src/test/java/org/springframework/cache/aspectj/AspectJEnableCachingIsolatedTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 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. @@ -120,7 +120,7 @@ public class AspectJEnableCachingIsolatedTests { load(EmptyConfig.class); } catch (IllegalStateException ex) { - assertTrue(ex.getMessage().contains("No bean of type CacheManager")); + assertTrue(ex.getMessage().contains("no bean of type CacheManager")); } } diff --git a/spring-aspects/src/test/java/org/springframework/scheduling/aspectj/AnnotationDrivenBeanDefinitionParserTests.java b/spring-aspects/src/test/java/org/springframework/scheduling/aspectj/AnnotationDrivenBeanDefinitionParserTests.java index 4b609c6e2c..c0f2ee8b1d 100644 --- a/spring-aspects/src/test/java/org/springframework/scheduling/aspectj/AnnotationDrivenBeanDefinitionParserTests.java +++ b/spring-aspects/src/test/java/org/springframework/scheduling/aspectj/AnnotationDrivenBeanDefinitionParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 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. @@ -16,6 +16,8 @@ package org.springframework.scheduling.aspectj; +import java.util.function.Supplier; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -56,14 +58,14 @@ public class AnnotationDrivenBeanDefinitionParserTests { public void asyncPostProcessorExecutorReference() { Object executor = context.getBean("testExecutor"); Object aspect = context.getBean(TaskManagementConfigUtils.ASYNC_EXECUTION_ASPECT_BEAN_NAME); - assertSame(executor, new DirectFieldAccessor(aspect).getPropertyValue("defaultExecutor")); + assertSame(executor, ((Supplier) new DirectFieldAccessor(aspect).getPropertyValue("defaultExecutor")).get()); } @Test public void asyncPostProcessorExceptionHandlerReference() { Object exceptionHandler = context.getBean("testExceptionHandler"); Object aspect = context.getBean(TaskManagementConfigUtils.ASYNC_EXECUTION_ASPECT_BEAN_NAME); - assertSame(exceptionHandler, new DirectFieldAccessor(aspect).getPropertyValue("exceptionHandler")); + assertSame(exceptionHandler, ((Supplier) new DirectFieldAccessor(aspect).getPropertyValue("exceptionHandler")).get()); } } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/config/AbstractJCacheConfiguration.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/config/AbstractJCacheConfiguration.java index e033b6436f..9c484cd333 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/config/AbstractJCacheConfiguration.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/config/AbstractJCacheConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -16,6 +16,8 @@ package org.springframework.cache.jcache.config; +import java.util.function.Supplier; + import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.cache.annotation.AbstractCachingConfiguration; import org.springframework.cache.annotation.CachingConfigurer; @@ -25,45 +27,37 @@ import org.springframework.cache.jcache.interceptor.JCacheOperationSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Role; +import org.springframework.lang.Nullable; /** * Abstract JSR-107 specific {@code @Configuration} class providing common * structure for enabling JSR-107 annotation-driven cache management capability. * * @author Stephane Nicoll + * @author Juergen Hoeller * @since 4.1 * @see JCacheConfigurer */ @Configuration public class AbstractJCacheConfiguration extends AbstractCachingConfiguration { - protected CacheResolver exceptionCacheResolver; + @Nullable + protected Supplier exceptionCacheResolver; + @Override protected void useCachingConfigurer(CachingConfigurer config) { super.useCachingConfigurer(config); if (config instanceof JCacheConfigurer) { - this.exceptionCacheResolver = ((JCacheConfigurer) config).exceptionCacheResolver(); + this.exceptionCacheResolver = ((JCacheConfigurer) config)::exceptionCacheResolver; } } @Bean(name = "jCacheOperationSource") @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public JCacheOperationSource cacheOperationSource() { - DefaultJCacheOperationSource source = new DefaultJCacheOperationSource(); - if (this.cacheManager != null) { - source.setCacheManager(this.cacheManager); - } - if (this.keyGenerator != null) { - source.setKeyGenerator(this.keyGenerator); - } - if (this.cacheResolver != null) { - source.setCacheResolver(this.cacheResolver); - } - if (this.exceptionCacheResolver != null) { - source.setExceptionCacheResolver(this.exceptionCacheResolver); - } - return source; + return new DefaultJCacheOperationSource( + this.cacheManager, this.cacheResolver, this.exceptionCacheResolver, this.keyGenerator); } } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/config/JCacheConfigurer.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/config/JCacheConfigurer.java index 784a42ae15..1c3e333d19 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/config/JCacheConfigurer.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/config/JCacheConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 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. @@ -18,6 +18,7 @@ package org.springframework.cache.jcache.config; import org.springframework.cache.annotation.CachingConfigurer; import org.springframework.cache.interceptor.CacheResolver; +import org.springframework.lang.Nullable; /** * Extension of {@link CachingConfigurer} for the JSR-107 implementation. @@ -58,6 +59,7 @@ public interface JCacheConfigurer extends CachingConfigurer { * * See {@link org.springframework.cache.annotation.EnableCaching} for more complete examples. */ + @Nullable CacheResolver exceptionCacheResolver(); } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/config/JCacheConfigurerSupport.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/config/JCacheConfigurerSupport.java index cd6adc7ef8..9e55f2596b 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/config/JCacheConfigurerSupport.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/config/JCacheConfigurerSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 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. @@ -18,6 +18,7 @@ package org.springframework.cache.jcache.config; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.interceptor.CacheResolver; +import org.springframework.lang.Nullable; /** * An extension of {@link CachingConfigurerSupport} that also implements @@ -34,6 +35,7 @@ import org.springframework.cache.interceptor.CacheResolver; public class JCacheConfigurerSupport extends CachingConfigurerSupport implements JCacheConfigurer { @Override + @Nullable public CacheResolver exceptionCacheResolver() { return null; } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/config/ProxyJCacheConfiguration.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/config/ProxyJCacheConfiguration.java index c7df156db5..0f8147d290 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/config/ProxyJCacheConfiguration.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/config/ProxyJCacheConfiguration.java @@ -31,6 +31,7 @@ import org.springframework.context.annotation.Role; *

Can safely be used alongside Spring's caching support. * * @author Stephane Nicoll + * @author Juergen Hoeller * @since 4.1 * @see org.springframework.cache.annotation.EnableCaching * @see org.springframework.cache.annotation.CachingConfigurationSelector @@ -55,11 +56,8 @@ public class ProxyJCacheConfiguration extends AbstractJCacheConfiguration { @Bean(name = "jCacheInterceptor") @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public JCacheInterceptor cacheInterceptor() { - JCacheInterceptor interceptor = new JCacheInterceptor(); + JCacheInterceptor interceptor = new JCacheInterceptor(this.errorHandler); interceptor.setCacheOperationSource(cacheOperationSource()); - if (this.errorHandler != null) { - interceptor.setErrorHandler(this.errorHandler); - } return interceptor; } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/config/package-info.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/config/package-info.java index 280ecee2fb..9bc89f8e0a 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/config/package-info.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/config/package-info.java @@ -6,4 +6,9 @@ *

Provide an extension of the {@code CachingConfigurer} that exposes * the exception cache resolver to use, see {@code JCacheConfigurer}. */ +@NonNullApi +@NonNullFields package org.springframework.cache.jcache.config; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractCacheInterceptor.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractCacheInterceptor.java index ab79b6c9c0..849cccc3dd 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractCacheInterceptor.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractCacheInterceptor.java @@ -51,6 +51,7 @@ abstract class AbstractCacheInterceptor, A } + @Nullable protected abstract Object invoke(CacheOperationInvocationContext context, CacheOperationInvoker invoker) throws Throwable; diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractFallbackJCacheOperationSource.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractFallbackJCacheOperationSource.java index 3ca53b2aeb..5e31deae5c 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractFallbackJCacheOperationSource.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractFallbackJCacheOperationSource.java @@ -55,7 +55,7 @@ public abstract class AbstractFallbackJCacheOperationSource implements JCacheOpe @Override - public JCacheOperation getCacheOperation(Method method, Class targetClass) { + public JCacheOperation getCacheOperation(Method method, @Nullable Class targetClass) { MethodClassKey cacheKey = new MethodClassKey(method, targetClass); Object cached = this.cache.get(cacheKey); @@ -78,7 +78,7 @@ public abstract class AbstractFallbackJCacheOperationSource implements JCacheOpe } @Nullable - private JCacheOperation computeCacheOperation(Method method, Class targetClass) { + private JCacheOperation computeCacheOperation(Method method, @Nullable Class targetClass) { // Don't allow no-public methods as required. if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { return null; @@ -113,7 +113,7 @@ public abstract class AbstractFallbackJCacheOperationSource implements JCacheOpe * (or {@code null} if none) */ @Nullable - protected abstract JCacheOperation findCacheOperation(Method method, Class targetType); + protected abstract JCacheOperation findCacheOperation(Method method, @Nullable Class targetType); /** * Should only public methods be allowed to have caching semantics? diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractJCacheOperation.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractJCacheOperation.java index a9b186a070..e879259df0 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractJCacheOperation.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractJCacheOperation.java @@ -50,7 +50,7 @@ abstract class AbstractJCacheOperation implements JCacheOp /** - * Create a new instance. + * Construct a new {@code AbstractJCacheOperation}. * @param methodDetails the {@link CacheMethodDetails} related to the cached method * @param cacheResolver the cache resolver to resolve regular caches */ diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractKeyCacheInterceptor.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractKeyCacheInterceptor.java index a7e85b4429..b6a0b43530 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractKeyCacheInterceptor.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractKeyCacheInterceptor.java @@ -39,6 +39,7 @@ abstract class AbstractKeyCacheInterceptor createCacheKeyInvocationContext( - CacheOperationInvocationContext context) { + protected CacheKeyInvocationContext createCacheKeyInvocationContext(CacheOperationInvocationContext context) { return new DefaultCacheKeyInvocationContext<>(context.getOperation(), context.getTarget(), context.getArgs()); } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AnnotationJCacheOperationSource.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AnnotationJCacheOperationSource.java index 0ebf93a71f..d574881141 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AnnotationJCacheOperationSource.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AnnotationJCacheOperationSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 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. @@ -45,7 +45,7 @@ import org.springframework.util.StringUtils; public abstract class AnnotationJCacheOperationSource extends AbstractFallbackJCacheOperationSource { @Override - protected JCacheOperation findCacheOperation(Method method, Class targetType) { + protected JCacheOperation findCacheOperation(Method method, @Nullable Class targetType) { CacheResult cacheResult = method.getAnnotation(CacheResult.class); CachePut cachePut = method.getAnnotation(CachePut.class); CacheRemove cacheRemove = method.getAnnotation(CacheRemove.class); @@ -74,15 +74,16 @@ public abstract class AnnotationJCacheOperationSource extends AbstractFallbackJC } } - protected CacheDefaults getCacheDefaults(Method method, Class targetType) { + @Nullable + protected CacheDefaults getCacheDefaults(Method method, @Nullable Class targetType) { CacheDefaults annotation = method.getDeclaringClass().getAnnotation(CacheDefaults.class); if (annotation != null) { return annotation; } - return targetType.getAnnotation(CacheDefaults.class); + return (targetType != null ? targetType.getAnnotation(CacheDefaults.class) : null); } - protected CacheResultOperation createCacheResultOperation(Method method, CacheDefaults defaults, CacheResult ann) { + protected CacheResultOperation createCacheResultOperation(Method method, @Nullable CacheDefaults defaults, CacheResult ann) { String cacheName = determineCacheName(method, defaults, ann.cacheName()); CacheResolverFactory cacheResolverFactory = determineCacheResolverFactory(defaults, ann.cacheResolverFactory()); @@ -100,7 +101,7 @@ public abstract class AnnotationJCacheOperationSource extends AbstractFallbackJC return new CacheResultOperation(methodDetails, cacheResolver, keyGenerator, exceptionCacheResolver); } - protected CachePutOperation createCachePutOperation(Method method, CacheDefaults defaults, CachePut ann) { + protected CachePutOperation createCachePutOperation(Method method, @Nullable CacheDefaults defaults, CachePut ann) { String cacheName = determineCacheName(method, defaults, ann.cacheName()); CacheResolverFactory cacheResolverFactory = determineCacheResolverFactory(defaults, ann.cacheResolverFactory()); @@ -111,7 +112,7 @@ public abstract class AnnotationJCacheOperationSource extends AbstractFallbackJC return new CachePutOperation(methodDetails, cacheResolver, keyGenerator); } - protected CacheRemoveOperation createCacheRemoveOperation(Method method, CacheDefaults defaults, CacheRemove ann) { + protected CacheRemoveOperation createCacheRemoveOperation(Method method, @Nullable CacheDefaults defaults, CacheRemove ann) { String cacheName = determineCacheName(method, defaults, ann.cacheName()); CacheResolverFactory cacheResolverFactory = determineCacheResolverFactory(defaults, ann.cacheResolverFactory()); @@ -122,7 +123,7 @@ public abstract class AnnotationJCacheOperationSource extends AbstractFallbackJC return new CacheRemoveOperation(methodDetails, cacheResolver, keyGenerator); } - protected CacheRemoveAllOperation createCacheRemoveAllOperation(Method method, CacheDefaults defaults, CacheRemoveAll ann) { + protected CacheRemoveAllOperation createCacheRemoveAllOperation(Method method, @Nullable CacheDefaults defaults, CacheRemoveAll ann) { String cacheName = determineCacheName(method, defaults, ann.cacheName()); CacheResolverFactory cacheResolverFactory = determineCacheResolverFactory(defaults, ann.cacheResolverFactory()); @@ -136,7 +137,9 @@ public abstract class AnnotationJCacheOperationSource extends AbstractFallbackJC return new DefaultCacheMethodDetails<>(method, annotation, cacheName); } - protected CacheResolver getCacheResolver(CacheResolverFactory factory, CacheMethodDetails details) { + protected CacheResolver getCacheResolver( + @Nullable CacheResolverFactory factory, CacheMethodDetails details) { + if (factory != null) { javax.cache.annotation.CacheResolver cacheResolver = factory.getCacheResolver(details); return new CacheResolverAdapter(cacheResolver); @@ -146,8 +149,8 @@ public abstract class AnnotationJCacheOperationSource extends AbstractFallbackJC } } - protected CacheResolver getExceptionCacheResolver(CacheResolverFactory factory, - CacheMethodDetails details) { + protected CacheResolver getExceptionCacheResolver( + @Nullable CacheResolverFactory factory, CacheMethodDetails details) { if (factory != null) { javax.cache.annotation.CacheResolver cacheResolver = factory.getExceptionCacheResolver(details); @@ -159,13 +162,13 @@ public abstract class AnnotationJCacheOperationSource extends AbstractFallbackJC } @Nullable - protected CacheResolverFactory determineCacheResolverFactory(CacheDefaults defaults, - Class candidate) { + protected CacheResolverFactory determineCacheResolverFactory( + @Nullable CacheDefaults defaults, Class candidate) { - if (CacheResolverFactory.class != candidate) { + if (candidate != CacheResolverFactory.class) { return getBean(candidate); } - else if (defaults != null && CacheResolverFactory.class != defaults.cacheResolverFactory()) { + else if (defaults != null && defaults.cacheResolverFactory() != CacheResolverFactory.class) { return getBean(defaults.cacheResolverFactory()); } else { @@ -173,8 +176,10 @@ public abstract class AnnotationJCacheOperationSource extends AbstractFallbackJC } } - protected KeyGenerator determineKeyGenerator(CacheDefaults defaults, Class candidate) { - if (CacheKeyGenerator.class != candidate) { + protected KeyGenerator determineKeyGenerator( + @Nullable CacheDefaults defaults, Class candidate) { + + if (candidate != CacheKeyGenerator.class) { return new KeyGeneratorAdapter(this, getBean(candidate)); } else if (defaults != null && CacheKeyGenerator.class != defaults.cacheKeyGenerator()) { @@ -185,7 +190,7 @@ public abstract class AnnotationJCacheOperationSource extends AbstractFallbackJC } } - protected String determineCacheName(Method method, CacheDefaults defaults, String candidate) { + protected String determineCacheName(Method method, @Nullable CacheDefaults defaults, String candidate) { if (StringUtils.hasText(candidate)) { return candidate; } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/BeanFactoryJCacheOperationSourceAdvisor.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/BeanFactoryJCacheOperationSourceAdvisor.java index 38bfa72bae..60e5f49a15 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/BeanFactoryJCacheOperationSourceAdvisor.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/BeanFactoryJCacheOperationSourceAdvisor.java @@ -19,6 +19,7 @@ package org.springframework.cache.jcache.interceptor; import org.springframework.aop.ClassFilter; import org.springframework.aop.Pointcut; import org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor; +import org.springframework.lang.Nullable; /** * Advisor driven by a {@link JCacheOperationSource}, used to include a @@ -30,6 +31,7 @@ import org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor; @SuppressWarnings("serial") public class BeanFactoryJCacheOperationSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor { + @Nullable private JCacheOperationSource cacheOperationSource; private final JCacheOperationSourcePointcut pointcut = new JCacheOperationSourcePointcut() { diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CachePutInterceptor.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CachePutInterceptor.java index ee339ce462..9df61af08a 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CachePutInterceptor.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CachePutInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 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. @@ -37,16 +37,16 @@ class CachePutInterceptor extends AbstractKeyCacheInterceptor context, - CacheOperationInvoker invoker) { - CacheKeyInvocationContext invocationContext = createCacheKeyInvocationContext(context); + @Override + protected Object invoke( + CacheOperationInvocationContext context, CacheOperationInvoker invoker) { + CachePutOperation operation = context.getOperation(); + CacheKeyInvocationContext invocationContext = createCacheKeyInvocationContext(context); boolean earlyPut = operation.isEarlyPut(); Object value = invocationContext.getValueParameter().getValue(); - if (earlyPut) { cacheValue(context, value); } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CachePutOperation.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CachePutOperation.java index 0df04db81a..847a6ce8a7 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CachePutOperation.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CachePutOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 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. @@ -24,6 +24,7 @@ import javax.cache.annotation.CachePut; import org.springframework.cache.interceptor.CacheResolver; import org.springframework.cache.interceptor.KeyGenerator; +import org.springframework.lang.Nullable; import org.springframework.util.ExceptionTypeFilter; /** @@ -44,13 +45,17 @@ class CachePutOperation extends AbstractJCacheKeyOperation { CacheMethodDetails methodDetails, CacheResolver cacheResolver, KeyGenerator keyGenerator) { super(methodDetails, cacheResolver, keyGenerator); + CachePut ann = methodDetails.getCacheAnnotation(); this.exceptionTypeFilter = createExceptionTypeFilter(ann.cacheFor(), ann.noCacheFor()); - this.valueParameterDetail = initializeValueParameterDetail(methodDetails.getMethod(), this.allParameterDetails); - if (this.valueParameterDetail == null) { + + CacheParameterDetail valueParameterDetail = + initializeValueParameterDetail(methodDetails.getMethod(), this.allParameterDetails); + if (valueParameterDetail == null) { throw new IllegalArgumentException("No parameter annotated with @CacheValue was found for " + - "" + methodDetails.getMethod()); + methodDetails.getMethod()); } + this.valueParameterDetail = valueParameterDetail; } @@ -85,6 +90,7 @@ class CachePutOperation extends AbstractJCacheKeyOperation { } + @Nullable private static CacheParameterDetail initializeValueParameterDetail( Method method, List allParameters) { diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheRemoveAllInterceptor.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheRemoveAllInterceptor.java index 78212f9bd8..c6ad61794d 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheRemoveAllInterceptor.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheRemoveAllInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 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. @@ -30,21 +30,20 @@ import org.springframework.cache.interceptor.CacheOperationInvoker; * @since 4.1 */ @SuppressWarnings("serial") -class CacheRemoveAllInterceptor - extends AbstractCacheInterceptor { +class CacheRemoveAllInterceptor extends AbstractCacheInterceptor { protected CacheRemoveAllInterceptor(CacheErrorHandler errorHandler) { super(errorHandler); } + @Override - protected Object invoke(CacheOperationInvocationContext context, - CacheOperationInvoker invoker) { + protected Object invoke( + CacheOperationInvocationContext context, CacheOperationInvoker invoker) { CacheRemoveAllOperation operation = context.getOperation(); boolean earlyRemove = operation.isEarlyRemove(); - if (earlyRemove) { removeAll(context); } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheRemoveEntryInterceptor.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheRemoveEntryInterceptor.java index 34d4fb4f05..681e6f598f 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheRemoveEntryInterceptor.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheRemoveEntryInterceptor.java @@ -36,13 +36,14 @@ class CacheRemoveEntryInterceptor extends AbstractKeyCacheInterceptor context, - CacheOperationInvoker invoker) { + protected Object invoke( + CacheOperationInvocationContext context, CacheOperationInvoker invoker) { + CacheRemoveOperation operation = context.getOperation(); - final boolean earlyRemove = operation.isEarlyRemove(); - + boolean earlyRemove = operation.isEarlyRemove(); if (earlyRemove) { removeValue(context); } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheResultInterceptor.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheResultInterceptor.java index 6cdf120ee7..54e7804bbb 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheResultInterceptor.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheResultInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -24,6 +24,7 @@ import org.springframework.cache.interceptor.CacheOperationInvocationContext; import org.springframework.cache.interceptor.CacheOperationInvoker; import org.springframework.cache.interceptor.CacheResolver; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import org.springframework.util.ExceptionTypeFilter; import org.springframework.util.SerializationUtils; @@ -42,8 +43,9 @@ class CacheResultInterceptor extends AbstractKeyCacheInterceptor context, - CacheOperationInvoker invoker) { + @Nullable + protected Object invoke( + CacheOperationInvocationContext context, CacheOperationInvoker invoker) { CacheResultOperation operation = context.getOperation(); Object cacheKey = generateKey(context); @@ -74,17 +76,19 @@ class CacheResultInterceptor extends AbstractKeyCacheInterceptor T cloneException(T exception) { try { return (T) SerializationUtils.deserialize(SerializationUtils.serialize(exception)); diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheResultOperation.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheResultOperation.java index f421dfe850..16cbe65a7a 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheResultOperation.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheResultOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 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. @@ -36,15 +36,18 @@ class CacheResultOperation extends AbstractJCacheKeyOperation { private final ExceptionTypeFilter exceptionTypeFilter; + @Nullable private final CacheResolver exceptionCacheResolver; + @Nullable private final String exceptionCacheName; public CacheResultOperation(CacheMethodDetails methodDetails, CacheResolver cacheResolver, - KeyGenerator keyGenerator, CacheResolver exceptionCacheResolver) { + KeyGenerator keyGenerator, @Nullable CacheResolver exceptionCacheResolver) { super(methodDetails, cacheResolver, keyGenerator); + CacheResult ann = methodDetails.getCacheAnnotation(); this.exceptionTypeFilter = createExceptionTypeFilter(ann.cachedExceptions(), ann.nonCachedExceptions()); this.exceptionCacheResolver = exceptionCacheResolver; @@ -70,6 +73,7 @@ class CacheResultOperation extends AbstractJCacheKeyOperation { * Return the {@link CacheResolver} instance to use to resolve the cache to * use for matching exceptions thrown by this operation. */ + @Nullable public CacheResolver getExceptionCacheResolver() { return this.exceptionCacheResolver; } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/DefaultCacheKeyInvocationContext.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/DefaultCacheKeyInvocationContext.java index 7f9d03fe09..1c49ea4e21 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/DefaultCacheKeyInvocationContext.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/DefaultCacheKeyInvocationContext.java @@ -20,6 +20,8 @@ import java.lang.annotation.Annotation; import javax.cache.annotation.CacheInvocationParameter; import javax.cache.annotation.CacheKeyInvocationContext; +import org.springframework.lang.Nullable; + /** * The default {@link CacheKeyInvocationContext} implementation. * @@ -32,10 +34,11 @@ class DefaultCacheKeyInvocationContext private final CacheInvocationParameter[] keyParameters; + @Nullable private final CacheInvocationParameter valueParameter; - public DefaultCacheKeyInvocationContext(AbstractJCacheKeyOperation operation, - Object target, Object[] args) { + + public DefaultCacheKeyInvocationContext(AbstractJCacheKeyOperation operation, Object target, Object[] args) { super(operation, target, args); this.keyParameters = operation.getKeyParameters(args); if (operation instanceof CachePutOperation) { @@ -46,12 +49,14 @@ class DefaultCacheKeyInvocationContext } } + @Override public CacheInvocationParameter[] getKeyParameters() { return this.keyParameters.clone(); } @Override + @Nullable public CacheInvocationParameter getValueParameter() { return this.valueParameter; } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/DefaultJCacheOperationSource.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/DefaultJCacheOperationSource.java index aebf919e86..f3571208a4 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/DefaultJCacheOperationSource.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/DefaultJCacheOperationSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 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. @@ -17,11 +17,11 @@ package org.springframework.cache.jcache.interceptor; import java.util.Collection; +import java.util.function.Supplier; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.SmartInitializingSingleton; @@ -34,6 +34,8 @@ import org.springframework.cache.interceptor.SimpleCacheResolver; import org.springframework.cache.interceptor.SimpleKeyGenerator; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.function.SingletonSupplier; +import org.springframework.util.function.SupplierUtils; /** * The default {@link JCacheOperationSource} implementation delegating @@ -41,30 +43,61 @@ import org.springframework.util.Assert; * when not present. * * @author Stephane Nicoll + * @author Juergen Hoeller * @since 4.1 */ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSource - implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton { + implements BeanFactoryAware, SmartInitializingSingleton { - private CacheManager cacheManager; + @Nullable + private SingletonSupplier cacheManager; - private CacheResolver cacheResolver; + @Nullable + private SingletonSupplier cacheResolver; - private CacheResolver exceptionCacheResolver; + @Nullable + private SingletonSupplier exceptionCacheResolver; - private KeyGenerator keyGenerator = new SimpleKeyGenerator(); + private SingletonSupplier keyGenerator; - private KeyGenerator adaptedKeyGenerator; + private final SingletonSupplier adaptedKeyGenerator = + SingletonSupplier.of(() -> new KeyGeneratorAdapter(this, getKeyGenerator())); + @Nullable private BeanFactory beanFactory; /** - * Set the default {@link CacheManager} to use to lookup cache by name. Only mandatory - * if the {@linkplain CacheResolver cache resolvers} have not been set. + * Construct a new {@code DefaultJCacheOperationSource} with the default key generator. + * @see SimpleKeyGenerator + */ + public DefaultJCacheOperationSource() { + this.keyGenerator = SingletonSupplier.of(SimpleKeyGenerator::new); + } + + /** + * Construct a new {@code DefaultJCacheOperationSource} with the given cache manager, + * cache resolver and key generator suppliers, applying the corresponding default + * if a supplier is not resolvable. + * @since 5.1 + */ + public DefaultJCacheOperationSource( + @Nullable Supplier cacheManager, @Nullable Supplier cacheResolver, + @Nullable Supplier exceptionCacheResolver, @Nullable Supplier keyGenerator) { + + this.cacheManager = SingletonSupplier.ofNullable(cacheManager); + this.cacheResolver = SingletonSupplier.ofNullable(cacheResolver); + this.exceptionCacheResolver = SingletonSupplier.ofNullable(exceptionCacheResolver); + this.keyGenerator = new SingletonSupplier<>(keyGenerator, SimpleKeyGenerator::new); + } + + + /** + * Set the default {@link CacheManager} to use to lookup cache by name. + * Only mandatory if the {@linkplain CacheResolver cache resolver} has not been set. */ public void setCacheManager(@Nullable CacheManager cacheManager) { - this.cacheManager = cacheManager; + this.cacheManager = SingletonSupplier.ofNullable(cacheManager); } /** @@ -72,7 +105,7 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc */ @Nullable public CacheManager getCacheManager() { - return this.cacheManager; + return SupplierUtils.resolve(this.cacheManager); } /** @@ -80,7 +113,7 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc * implementation using the specified cache manager will be used. */ public void setCacheResolver(@Nullable CacheResolver cacheResolver) { - this.cacheResolver = cacheResolver; + this.cacheResolver = SingletonSupplier.ofNullable(cacheResolver); } /** @@ -88,7 +121,7 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc */ @Nullable public CacheResolver getCacheResolver() { - return this.cacheResolver; + return SupplierUtils.resolve(this.cacheResolver); } /** @@ -96,7 +129,7 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc * implementation using the specified cache manager will be used. */ public void setExceptionCacheResolver(@Nullable CacheResolver exceptionCacheResolver) { - this.exceptionCacheResolver = exceptionCacheResolver; + this.exceptionCacheResolver = SingletonSupplier.ofNullable(exceptionCacheResolver); } /** @@ -104,7 +137,7 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc */ @Nullable public CacheResolver getExceptionCacheResolver() { - return this.exceptionCacheResolver; + return SupplierUtils.resolve(this.exceptionCacheResolver); } /** @@ -112,16 +145,15 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc * honoring the JSR-107 {@link javax.cache.annotation.CacheKey} and * {@link javax.cache.annotation.CacheValue} will be used. */ - public void setKeyGenerator(@Nullable KeyGenerator keyGenerator) { - this.keyGenerator = keyGenerator; + public void setKeyGenerator(KeyGenerator keyGenerator) { + this.keyGenerator = SingletonSupplier.of(keyGenerator); } /** - * Return the specified key generator to use, if any. + * Return the specified key generator to use. */ - @Nullable public KeyGenerator getKeyGenerator() { - return this.keyGenerator; + return this.keyGenerator.obtain(); } @Override @@ -130,21 +162,17 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc } - @Override - public void afterPropertiesSet() { - this.adaptedKeyGenerator = new KeyGeneratorAdapter(this, this.keyGenerator); - } - @Override public void afterSingletonsInstantiated() { // Make sure that the cache resolver is initialized. An exception cache resolver is only - // required if the exceptionCacheName attribute is set on an operation + // required if the exceptionCacheName attribute is set on an operation. Assert.notNull(getDefaultCacheResolver(), "Cache resolver should have been initialized"); } @Override protected T getBean(Class type) { + Assert.state(this.beanFactory != null, "BeanFactory required for resolution of [" + type + "]"); try { return this.beanFactory.getBean(type); } @@ -161,9 +189,10 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc } protected CacheManager getDefaultCacheManager() { - if (this.cacheManager == null) { + if (getCacheManager() == null) { + Assert.state(this.beanFactory != null, "BeanFactory required for default CacheManager resolution"); try { - this.cacheManager = this.beanFactory.getBean(CacheManager.class); + this.cacheManager = SingletonSupplier.of(this.beanFactory.getBean(CacheManager.class)); } catch (NoUniqueBeanDefinitionException ex) { throw new IllegalStateException("No unique bean of type CacheManager found. "+ @@ -174,50 +203,50 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc "bean or remove the @EnableCaching annotation from your configuration."); } } - return this.cacheManager; + return getCacheManager(); } @Override protected CacheResolver getDefaultCacheResolver() { - if (this.cacheResolver == null) { - this.cacheResolver = new SimpleCacheResolver(getDefaultCacheManager()); + if (getCacheResolver() == null) { + this.cacheResolver = SingletonSupplier.of(new SimpleCacheResolver(getDefaultCacheManager())); } - return this.cacheResolver; + return getCacheResolver(); } @Override protected CacheResolver getDefaultExceptionCacheResolver() { - if (this.exceptionCacheResolver == null) { - this.exceptionCacheResolver = new LazyCacheResolver(); + if (getExceptionCacheResolver() == null) { + this.exceptionCacheResolver = SingletonSupplier.of(new LazyCacheResolver()); } - return this.exceptionCacheResolver; + return getExceptionCacheResolver(); } @Override protected KeyGenerator getDefaultKeyGenerator() { - return this.adaptedKeyGenerator; + return this.adaptedKeyGenerator.obtain(); } /** * Only resolve the default exception cache resolver when an exception needs to be handled. - *

A non-JSR-107 setup requires either a {@link CacheManager} or a {@link CacheResolver}. If only - * the latter is specified, it is not possible to extract a default exception {@code CacheResolver} - * from a custom {@code CacheResolver} implementation so we have to fallback on the {@code CacheManager}. - *

This gives this weird situation of a perfectly valid configuration that breaks all the sudden - * because the JCache support is enabled. To avoid this we resolve the default exception {@code CacheResolver} - * as late as possible to avoid such hard requirement in other cases. + *

A non-JSR-107 setup requires either a {@link CacheManager} or a {@link CacheResolver}. + * If only the latter is specified, it is not possible to extract a default exception + * {@code CacheResolver} from a custom {@code CacheResolver} implementation so we have to + * fall back on the {@code CacheManager}. + *

This gives this weird situation of a perfectly valid configuration that breaks all + * the sudden because the JCache support is enabled. To avoid this we resolve the default + * exception {@code CacheResolver} as late as possible to avoid such hard requirement + * in other cases. */ class LazyCacheResolver implements CacheResolver { - private CacheResolver cacheResolver; + private final SingletonSupplier cacheResolver = + SingletonSupplier.of(() -> new SimpleExceptionCacheResolver(getDefaultCacheManager())); @Override public Collection resolveCaches(CacheOperationInvocationContext context) { - if (this.cacheResolver == null) { - this.cacheResolver = new SimpleExceptionCacheResolver(getDefaultCacheManager()); - } - return this.cacheResolver.resolveCaches(context); + return this.cacheResolver.obtain().resolveCaches(context); } } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheAspectSupport.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheAspectSupport.java index 21464e1265..121de7492f 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheAspectSupport.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheAspectSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -28,6 +28,7 @@ import org.springframework.cache.interceptor.AbstractCacheInvoker; import org.springframework.cache.interceptor.BasicOperation; import org.springframework.cache.interceptor.CacheOperationInvocationContext; import org.springframework.cache.interceptor.CacheOperationInvoker; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -52,19 +53,27 @@ public class JCacheAspectSupport extends AbstractCacheInvoker implements Initial protected final Log logger = LogFactory.getLog(getClass()); + @Nullable private JCacheOperationSource cacheOperationSource; + @Nullable + private CacheResultInterceptor cacheResultInterceptor; + + @Nullable + private CachePutInterceptor cachePutInterceptor; + + @Nullable + private CacheRemoveEntryInterceptor cacheRemoveEntryInterceptor; + + @Nullable + private CacheRemoveAllInterceptor cacheRemoveAllInterceptor; + private boolean initialized = false; - private CacheResultInterceptor cacheResultInterceptor; - - private CachePutInterceptor cachePutInterceptor; - - private CacheRemoveEntryInterceptor cacheRemoveEntryInterceptor; - - private CacheRemoveAllInterceptor cacheRemoveAllInterceptor; - + /** + * Set the CacheOperationSource for this cache aspect. + */ public void setCacheOperationSource(JCacheOperationSource cacheOperationSource) { Assert.notNull(cacheOperationSource, "JCacheOperationSource must not be null"); this.cacheOperationSource = cacheOperationSource; @@ -74,12 +83,13 @@ public class JCacheAspectSupport extends AbstractCacheInvoker implements Initial * Return the CacheOperationSource for this cache aspect. */ public JCacheOperationSource getCacheOperationSource() { + Assert.state(this.cacheOperationSource != null, "The 'cacheOperationSource' property is required: " + + "If there are no cacheable methods, then don't use a cache aspect."); return this.cacheOperationSource; } public void afterPropertiesSet() { - Assert.state(getCacheOperationSource() != null, "The 'cacheOperationSource' property is required: " + - "If there are no cacheable methods, then don't use a cache aspect."); + getCacheOperationSource(); this.cacheResultInterceptor = new CacheResultInterceptor(getErrorHandler()); this.cachePutInterceptor = new CachePutInterceptor(getErrorHandler()); @@ -90,6 +100,7 @@ public class JCacheAspectSupport extends AbstractCacheInvoker implements Initial } + @Nullable protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) { // Check whether aspect is enabled to cope with cases where the AJ is pulled in automatically if (this.initialized) { @@ -114,23 +125,28 @@ public class JCacheAspectSupport extends AbstractCacheInvoker implements Initial } @SuppressWarnings("unchecked") + @Nullable private Object execute(CacheOperationInvocationContext context, CacheOperationInvoker invoker) { CacheOperationInvoker adapter = new CacheOperationInvokerAdapter(invoker); BasicOperation operation = context.getOperation(); if (operation instanceof CacheResultOperation) { + Assert.state(this.cacheResultInterceptor != null, "No CacheResultInterceptor"); return this.cacheResultInterceptor.invoke( (CacheOperationInvocationContext) context, adapter); } else if (operation instanceof CachePutOperation) { + Assert.state(this.cachePutInterceptor != null, "No CachePutInterceptor"); return this.cachePutInterceptor.invoke( (CacheOperationInvocationContext) context, adapter); } else if (operation instanceof CacheRemoveOperation) { + Assert.state(this.cacheRemoveEntryInterceptor != null, "No CacheRemoveEntryInterceptor"); return this.cacheRemoveEntryInterceptor.invoke( (CacheOperationInvocationContext) context, adapter); } else if (operation instanceof CacheRemoveAllOperation) { + Assert.state(this.cacheRemoveAllInterceptor != null, "No CacheRemoveAllInterceptor"); return this.cacheRemoveAllInterceptor.invoke( (CacheOperationInvocationContext) context, adapter); } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheInterceptor.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheInterceptor.java index f478f10e97..532c973273 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheInterceptor.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -18,11 +18,16 @@ package org.springframework.cache.jcache.interceptor; import java.io.Serializable; import java.lang.reflect.Method; +import java.util.function.Supplier; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; +import org.springframework.cache.interceptor.CacheErrorHandler; import org.springframework.cache.interceptor.CacheOperationInvoker; +import org.springframework.cache.interceptor.SimpleCacheErrorHandler; +import org.springframework.lang.Nullable; +import org.springframework.util.function.SingletonSupplier; /** * AOP Alliance MethodInterceptor for declarative cache @@ -35,13 +40,32 @@ import org.springframework.cache.interceptor.CacheOperationInvoker; *

JCacheInterceptors are thread-safe. * * @author Stephane Nicoll + * @author Juergen Hoeller * @since 4.1 * @see org.springframework.cache.interceptor.CacheInterceptor */ @SuppressWarnings("serial") public class JCacheInterceptor extends JCacheAspectSupport implements MethodInterceptor, Serializable { + /** + * Construct a new {@code JCacheInterceptor} with the default error handler. + */ + public JCacheInterceptor() { + } + + /** + * Construct a new {@code JCacheInterceptor} with the given error handler. + * @param errorHandler a supplier for the error handler to use, + * applying the default error handler if the supplier is not resolvable + * @since 5.1 + */ + public JCacheInterceptor(@Nullable Supplier errorHandler) { + this.errorHandler = new SingletonSupplier<>(errorHandler, SimpleCacheErrorHandler::new); + } + + @Override + @Nullable public Object invoke(final MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheOperationSource.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheOperationSource.java index 5139282c1d..43068e7fba 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheOperationSource.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheOperationSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 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. @@ -33,7 +33,6 @@ public interface JCacheOperationSource { /** * Return the cache operations for this method, or {@code null} * if the method contains no JSR-107 related metadata. - * * @param method the method to introspect * @param targetClass the target class (may be {@code null}, in which case * the declaring class of the method must be used) diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheOperationSourcePointcut.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheOperationSourcePointcut.java index 46fbd7439b..f638c1f987 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheOperationSourcePointcut.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheOperationSourcePointcut.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 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. @@ -31,11 +31,10 @@ import org.springframework.util.ObjectUtils; * @since 4.1 */ @SuppressWarnings("serial") -public abstract class JCacheOperationSourcePointcut - extends StaticMethodMatcherPointcut implements Serializable { +public abstract class JCacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable { @Override - public boolean matches(Method method, Class targetClass) { + public boolean matches(Method method, @Nullable Class targetClass) { JCacheOperationSource cas = getCacheOperationSource(); return (cas != null && cas.getCacheOperation(method, targetClass) != null); } @@ -47,6 +46,7 @@ public abstract class JCacheOperationSourcePointcut @Nullable protected abstract JCacheOperationSource getCacheOperationSource(); + @Override public boolean equals(Object other) { if (this == other) { diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/KeyGeneratorAdapter.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/KeyGeneratorAdapter.java index 679b5bec2c..fb00ab22a2 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/KeyGeneratorAdapter.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/KeyGeneratorAdapter.java @@ -25,6 +25,7 @@ import javax.cache.annotation.CacheKeyGenerator; import javax.cache.annotation.CacheKeyInvocationContext; import org.springframework.cache.interceptor.KeyGenerator; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -34,14 +35,17 @@ import org.springframework.util.CollectionUtils; * so that only relevant parameters are handled. * * @author Stephane Nicoll + * @author Juergen Hoeller * @since 4.1 */ class KeyGeneratorAdapter implements KeyGenerator { private final JCacheOperationSource cacheOperationSource; + @Nullable private KeyGenerator keyGenerator; + @Nullable private CacheKeyGenerator cacheKeyGenerator; @@ -72,7 +76,11 @@ class KeyGeneratorAdapter implements KeyGenerator { * or a {@link CacheKeyGenerator}. */ public Object getTarget() { - return (this.keyGenerator != null ? this.keyGenerator : this.cacheKeyGenerator); + if (this.cacheKeyGenerator != null) { + return this.cacheKeyGenerator; + } + Assert.state(this.keyGenerator != null, "No key generator"); + return this.keyGenerator; } @Override @@ -87,6 +95,7 @@ class KeyGeneratorAdapter implements KeyGenerator { return this.cacheKeyGenerator.generateCacheKey(invocationContext); } else { + Assert.state(this.keyGenerator != null, "No key generator"); return doGenerate(this.keyGenerator, invocationContext); } } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/package-info.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/package-info.java index 9240434b37..c752c806b9 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/package-info.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/package-info.java @@ -7,4 +7,9 @@ *

Builds on the AOP infrastructure in org.springframework.aop.framework. * Any POJO can be cache-advised with Spring. */ +@NonNullApi +@NonNullFields package org.springframework.cache.jcache.interceptor; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/AnnotationCacheOperationSourceTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/AnnotationCacheOperationSourceTests.java index 67c4d82770..81c4256b17 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/AnnotationCacheOperationSourceTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/AnnotationCacheOperationSourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 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. @@ -47,16 +47,15 @@ public class AnnotationCacheOperationSourceTests extends AbstractJCacheTests { private final DefaultJCacheOperationSource source = new DefaultJCacheOperationSource(); - private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); @Before - public void setUp() { + public void setup() { source.setCacheResolver(defaultCacheResolver); source.setExceptionCacheResolver(defaultExceptionCacheResolver); source.setKeyGenerator(defaultKeyGenerator); source.setBeanFactory(beanFactory); - source.afterPropertiesSet(); } diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheInterceptorTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheInterceptorTests.java index 32eb6148e7..029db9114b 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheInterceptorTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheInterceptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 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. @@ -113,7 +113,6 @@ public class JCacheInterceptorTests extends AbstractJCacheTests { source.setExceptionCacheResolver(exceptionCacheResolver); source.setKeyGenerator(keyGenerator); source.setBeanFactory(new StaticListableBeanFactory()); - source.afterPropertiesSet(); source.afterSingletonsInstantiated(); return source; } diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java b/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java index d4c42d5647..c652ec00a0 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -17,6 +17,7 @@ package org.springframework.cache.annotation; import java.util.Collection; +import java.util.function.Supplier; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.CacheManager; @@ -36,6 +37,7 @@ import org.springframework.util.CollectionUtils; * * @author Chris Beams * @author Stephane Nicoll + * @author Juergen Hoeller * @since 3.1 * @see EnableCaching */ @@ -46,16 +48,16 @@ public abstract class AbstractCachingConfiguration implements ImportAware { protected AnnotationAttributes enableCaching; @Nullable - protected CacheManager cacheManager; + protected Supplier cacheManager; @Nullable - protected CacheResolver cacheResolver; + protected Supplier cacheResolver; @Nullable - protected KeyGenerator keyGenerator; + protected Supplier keyGenerator; @Nullable - protected CacheErrorHandler errorHandler; + protected Supplier errorHandler; @Override @@ -87,10 +89,10 @@ public abstract class AbstractCachingConfiguration implements ImportAware { * Extract the configuration from the nominated {@link CachingConfigurer}. */ protected void useCachingConfigurer(CachingConfigurer config) { - this.cacheManager = config.cacheManager(); - this.cacheResolver = config.cacheResolver(); - this.keyGenerator = config.keyGenerator(); - this.errorHandler = config.errorHandler(); + this.cacheManager = config::cacheManager; + this.cacheResolver = config::cacheResolver; + this.keyGenerator = config::keyGenerator; + this.errorHandler = config::errorHandler; } } diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurerSupport.java b/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurerSupport.java index 6719efa137..30a929709e 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurerSupport.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurerSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 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. @@ -40,13 +40,13 @@ public class CachingConfigurerSupport implements CachingConfigurer { @Override @Nullable - public KeyGenerator keyGenerator() { + public CacheResolver cacheResolver() { return null; } @Override @Nullable - public CacheResolver cacheResolver() { + public KeyGenerator keyGenerator() { return null; } diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/ProxyCachingConfiguration.java b/spring-context/src/main/java/org/springframework/cache/annotation/ProxyCachingConfiguration.java index 4d25771f19..f3f1e32c93 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/ProxyCachingConfiguration.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/ProxyCachingConfiguration.java @@ -30,6 +30,7 @@ import org.springframework.context.annotation.Role; * to enable proxy-based annotation-driven cache management. * * @author Chris Beams + * @author Juergen Hoeller * @since 3.1 * @see EnableCaching * @see CachingConfigurationSelector @@ -61,19 +62,8 @@ public class ProxyCachingConfiguration extends AbstractCachingConfiguration { @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public CacheInterceptor cacheInterceptor() { CacheInterceptor interceptor = new CacheInterceptor(); - interceptor.setCacheOperationSources(cacheOperationSource()); - if (this.cacheResolver != null) { - interceptor.setCacheResolver(this.cacheResolver); - } - else if (this.cacheManager != null) { - interceptor.setCacheManager(this.cacheManager); - } - if (this.keyGenerator != null) { - interceptor.setKeyGenerator(this.keyGenerator); - } - if (this.errorHandler != null) { - interceptor.setErrorHandler(this.errorHandler); - } + interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager); + interceptor.setCacheOperationSource(cacheOperationSource()); return interceptor; } diff --git a/spring-context/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java b/spring-context/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java index 1174ef1032..09ba164238 100644 --- a/spring-context/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java +++ b/spring-context/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java @@ -112,21 +112,21 @@ class AnnotationDrivenCacheBeanDefinitionParser implements BeanDefinitionParser */ private static void parseCacheResolution(Element element, BeanDefinition def, boolean setBoth) { String name = element.getAttribute("cache-resolver"); - if (StringUtils.hasText(name)) { + boolean hasText = StringUtils.hasText(name); + if (hasText) { def.getPropertyValues().add("cacheResolver", new RuntimeBeanReference(name.trim())); } - if (!StringUtils.hasText(name) || setBoth) { + if (!hasText || setBoth) { def.getPropertyValues().add("cacheManager", new RuntimeBeanReference(CacheNamespaceHandler.extractCacheManager(element))); } } - private static BeanDefinition parseErrorHandler(Element element, BeanDefinition def) { + private static void parseErrorHandler(Element element, BeanDefinition def) { String name = element.getAttribute("error-handler"); if (StringUtils.hasText(name)) { def.getPropertyValues().add("errorHandler", new RuntimeBeanReference(name.trim())); } - return def; } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractCacheInvoker.java b/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractCacheInvoker.java index 53fe359c04..c075c2ac2f 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractCacheInvoker.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractCacheInvoker.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -18,27 +18,28 @@ package org.springframework.cache.interceptor; import org.springframework.cache.Cache; import org.springframework.lang.Nullable; -import org.springframework.util.Assert; +import org.springframework.util.function.SingletonSupplier; /** * A base component for invoking {@link Cache} operations and using a * configurable {@link CacheErrorHandler} when an exception occurs. * * @author Stephane Nicoll + * @author Juergen Hoeller * @since 4.1 * @see org.springframework.cache.interceptor.CacheErrorHandler */ public abstract class AbstractCacheInvoker { - private CacheErrorHandler errorHandler; + protected SingletonSupplier errorHandler; protected AbstractCacheInvoker() { - this.errorHandler = new SimpleCacheErrorHandler(); + this.errorHandler = SingletonSupplier.of(SimpleCacheErrorHandler::new); } protected AbstractCacheInvoker(CacheErrorHandler errorHandler) { - this.errorHandler = errorHandler; + this.errorHandler = SingletonSupplier.of(errorHandler); } @@ -48,15 +49,14 @@ public abstract class AbstractCacheInvoker { * is used who throws any exception as is. */ public void setErrorHandler(CacheErrorHandler errorHandler) { - Assert.notNull(errorHandler, "CacheErrorHandler must not be null"); - this.errorHandler = errorHandler; + this.errorHandler = SingletonSupplier.of(errorHandler); } /** * Return the {@link CacheErrorHandler} to use. */ public CacheErrorHandler getErrorHandler() { - return this.errorHandler; + return this.errorHandler.obtain(); } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractCacheResolver.java b/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractCacheResolver.java index 815926be2d..d3c15b4d7c 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractCacheResolver.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractCacheResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -40,9 +40,17 @@ public abstract class AbstractCacheResolver implements CacheResolver, Initializi private CacheManager cacheManager; + /** + * Construct a new {@code AbstractCacheResolver}. + * @see #setCacheManager + */ protected AbstractCacheResolver() { } + /** + * Construct a new {@code AbstractCacheResolver} for the given {@link CacheManager}. + * @param cacheManager the CacheManager to use + */ protected AbstractCacheResolver(CacheManager cacheManager) { this.cacheManager = cacheManager; } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java index 454d2f2782..bef068079e 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -52,6 +53,8 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; +import org.springframework.util.function.SingletonSupplier; +import org.springframework.util.function.SupplierUtils; /** * Base class for caching aspects, such as the {@link CacheInterceptor} or an @@ -89,10 +92,10 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker @Nullable private CacheOperationSource cacheOperationSource; - private KeyGenerator keyGenerator = new SimpleKeyGenerator(); + private SingletonSupplier keyGenerator = SingletonSupplier.of(SimpleKeyGenerator::new); @Nullable - private CacheResolver cacheResolver; + private SingletonSupplier cacheResolver; @Nullable private BeanFactory beanFactory; @@ -100,10 +103,27 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker private boolean initialized = false; + /** + * Configure this aspect with the given error handler, key generator and cache resolver/manager + * suppliers, applying the corresponding default if a supplier is not resolvable. + * @since 5.1 + */ + public void configure( + @Nullable Supplier errorHandler, @Nullable Supplier keyGenerator, + @Nullable Supplier cacheResolver, @Nullable Supplier cacheManager) { + + this.errorHandler = new SingletonSupplier<>(errorHandler, SimpleCacheErrorHandler::new); + this.keyGenerator = new SingletonSupplier<>(keyGenerator, SimpleKeyGenerator::new); + this.cacheResolver = new SingletonSupplier<>(cacheResolver, + () -> SimpleCacheResolver.of(SupplierUtils.resolve(cacheManager))); + } + + /** * Set one or more cache operation sources which are used to find the cache * attributes. If more than one source is provided, they will be aggregated * using a {@link CompositeCacheOperationSource}. + * @see #setCacheOperationSource */ public void setCacheOperationSources(CacheOperationSource... cacheOperationSources) { Assert.notEmpty(cacheOperationSources, "At least 1 CacheOperationSource needs to be specified"); @@ -111,6 +131,15 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker new CompositeCacheOperationSource(cacheOperationSources) : cacheOperationSources[0]); } + /** + * Set the CacheOperationSource for this cache aspect. + * @since 5.1 + * @see #setCacheOperationSources + */ + public void setCacheOperationSource(@Nullable CacheOperationSource cacheOperationSource) { + this.cacheOperationSource = cacheOperationSource; + } + /** * Return the CacheOperationSource for this cache aspect. */ @@ -125,14 +154,14 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker *

The default is a {@link SimpleKeyGenerator}. */ public void setKeyGenerator(KeyGenerator keyGenerator) { - this.keyGenerator = keyGenerator; + this.keyGenerator = SingletonSupplier.of(keyGenerator); } /** * Return the default {@link KeyGenerator} that this cache aspect delegates to. */ public KeyGenerator getKeyGenerator() { - return this.keyGenerator; + return this.keyGenerator.obtain(); } /** @@ -144,7 +173,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker * @see SimpleCacheResolver */ public void setCacheResolver(@Nullable CacheResolver cacheResolver) { - this.cacheResolver = cacheResolver; + this.cacheResolver = SingletonSupplier.ofNullable(cacheResolver); } /** @@ -152,7 +181,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker */ @Nullable public CacheResolver getCacheResolver() { - return this.cacheResolver; + return SupplierUtils.resolve(this.cacheResolver); } /** @@ -162,7 +191,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker * @see SimpleCacheResolver */ public void setCacheManager(CacheManager cacheManager) { - this.cacheResolver = new SimpleCacheResolver(cacheManager); + this.cacheResolver = SingletonSupplier.of(new SimpleCacheResolver(cacheManager)); } /** @@ -192,8 +221,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker } catch (NoUniqueBeanDefinitionException ex) { throw new IllegalStateException("No CacheResolver specified, and no unique bean of type " + - "CacheManager found. Mark one as primary (or give it the name 'cacheManager') or " + - "declare a specific CacheManager to use, that serves as the default one."); + "CacheManager found. Mark one as primary or declare a specific CacheManager to use."); } catch (NoSuchBeanDefinitionException ex) { throw new IllegalStateException("No CacheResolver specified, and no bean of type CacheManager found. " + diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleCacheResolver.java b/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleCacheResolver.java index ae6083c453..c8991b950f 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleCacheResolver.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleCacheResolver.java @@ -20,6 +20,7 @@ import java.util.Collection; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; +import org.springframework.lang.Nullable; /** * A simple {@link CacheResolver} that resolves the {@link Cache} instance(s) @@ -27,14 +28,23 @@ import org.springframework.cache.CacheManager; * cache(s) as provided by {@link BasicOperation#getCacheNames() getCacheNames()}. * * @author Stephane Nicoll + * @author Juergen Hoeller * @since 4.1 * @see BasicOperation#getCacheNames() */ public class SimpleCacheResolver extends AbstractCacheResolver { + /** + * Construct a new {@code SimpleCacheResolver}. + * @see #setCacheManager + */ public SimpleCacheResolver() { } + /** + * Construct a new {@code SimpleCacheResolver} for the given {@link CacheManager}. + * @param cacheManager the CacheManager to use + */ public SimpleCacheResolver(CacheManager cacheManager) { super(cacheManager); } @@ -45,4 +55,16 @@ public class SimpleCacheResolver extends AbstractCacheResolver { return context.getOperation().getCacheNames(); } + + /** + * Return a {@code SimpleCacheResolver} for the given {@link CacheManager}. + * @param cacheManager the CacheManager (potentially {@code null}) + * @return the SimpleCacheResolver ({@code null} if the CacheManager was {@code null}) + * @since 5.1 + */ + @Nullable + static SimpleCacheResolver of(@Nullable CacheManager cacheManager) { + return (cacheManager != null ? new SimpleCacheResolver(cacheManager) : null); + } + } diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AbstractAsyncConfiguration.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AbstractAsyncConfiguration.java index b79e3947a6..0cf0e5ce7c 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AbstractAsyncConfiguration.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AbstractAsyncConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -18,6 +18,7 @@ package org.springframework.scheduling.annotation; import java.util.Collection; import java.util.concurrent.Executor; +import java.util.function.Supplier; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.beans.factory.annotation.Autowired; @@ -33,6 +34,7 @@ import org.springframework.util.CollectionUtils; * Spring's asynchronous method execution capability. * * @author Chris Beams + * @author Juergen Hoeller * @author Stephane Nicoll * @since 3.1 * @see EnableAsync @@ -44,10 +46,10 @@ public abstract class AbstractAsyncConfiguration implements ImportAware { protected AnnotationAttributes enableAsync; @Nullable - protected Executor executor; + protected Supplier executor; @Nullable - protected AsyncUncaughtExceptionHandler exceptionHandler; + protected Supplier exceptionHandler; @Override @@ -72,8 +74,8 @@ public abstract class AbstractAsyncConfiguration implements ImportAware { throw new IllegalStateException("Only one AsyncConfigurer may exist"); } AsyncConfigurer configurer = configurers.iterator().next(); - this.executor = configurer.getAsyncExecutor(); - this.exceptionHandler = configurer.getAsyncUncaughtExceptionHandler(); + this.executor = configurer::getAsyncExecutor; + this.exceptionHandler = configurer::getAsyncUncaughtExceptionHandler; } } diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java index f04efd853e..80b49822b2 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -21,12 +21,12 @@ import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; import java.util.concurrent.Executor; +import java.util.function.Supplier; import org.aopalliance.aop.Advice; import org.springframework.aop.Pointcut; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; -import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler; import org.springframework.aop.support.AbstractPointcutAdvisor; import org.springframework.aop.support.ComposablePointcut; import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; @@ -35,6 +35,7 @@ import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.function.SingletonSupplier; /** * Advisor that activates asynchronous method execution through the {@link Async} @@ -54,8 +55,6 @@ import org.springframework.util.ClassUtils; @SuppressWarnings("serial") public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware { - private AsyncUncaughtExceptionHandler exceptionHandler; - private Advice advice; private Pointcut pointcut; @@ -65,7 +64,7 @@ public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements B * Create a new {@code AsyncAnnotationAdvisor} for bean-style configuration. */ public AsyncAnnotationAdvisor() { - this(null, null); + this((Supplier) null, (Supplier) null); } /** @@ -77,7 +76,25 @@ public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements B * @see AnnotationAsyncExecutionInterceptor#getDefaultExecutor(BeanFactory) */ @SuppressWarnings("unchecked") - public AsyncAnnotationAdvisor(@Nullable Executor executor, @Nullable AsyncUncaughtExceptionHandler exceptionHandler) { + public AsyncAnnotationAdvisor( + @Nullable Executor executor, @Nullable AsyncUncaughtExceptionHandler exceptionHandler) { + + this(SingletonSupplier.ofNullable(executor), SingletonSupplier.ofNullable(exceptionHandler)); + } + + /** + * Create a new {@code AsyncAnnotationAdvisor} for the given task executor. + * @param executor the task executor to use for asynchronous methods + * (can be {@code null} to trigger default executor resolution) + * @param exceptionHandler the {@link AsyncUncaughtExceptionHandler} to use to + * handle unexpected exception thrown by asynchronous method executions + * @since 5.1 + * @see AnnotationAsyncExecutionInterceptor#getDefaultExecutor(BeanFactory) + */ + @SuppressWarnings("unchecked") + public AsyncAnnotationAdvisor( + @Nullable Supplier executor, @Nullable Supplier exceptionHandler) { + Set> asyncAnnotationTypes = new LinkedHashSet<>(2); asyncAnnotationTypes.add(Async.class); try { @@ -87,24 +104,11 @@ public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements B catch (ClassNotFoundException ex) { // If EJB 3.1 API not present, simply ignore. } - if (exceptionHandler != null) { - this.exceptionHandler = exceptionHandler; - } - else { - this.exceptionHandler = new SimpleAsyncUncaughtExceptionHandler(); - } - this.advice = buildAdvice(executor, this.exceptionHandler); + this.advice = buildAdvice(executor, exceptionHandler); this.pointcut = buildPointcut(asyncAnnotationTypes); } - /** - * Specify the default task executor to use for asynchronous methods. - */ - public void setTaskExecutor(Executor executor) { - this.advice = buildAdvice(executor, this.exceptionHandler); - } - /** * Set the 'async' annotation type. *

The default async annotation type is the {@link Async} annotation, as well @@ -143,8 +147,12 @@ public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements B } - protected Advice buildAdvice(@Nullable Executor executor, AsyncUncaughtExceptionHandler exceptionHandler) { - return new AnnotationAsyncExecutionInterceptor(executor, exceptionHandler); + protected Advice buildAdvice( + @Nullable Supplier executor, @Nullable Supplier exceptionHandler) { + + AnnotationAsyncExecutionInterceptor interceptor = new AnnotationAsyncExecutionInterceptor(null); + interceptor.configure(executor, exceptionHandler); + return interceptor; } /** diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java index 3b2446507b..8c75abb1fb 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -18,6 +18,7 @@ package org.springframework.scheduling.annotation; import java.lang.annotation.Annotation; import java.util.concurrent.Executor; +import java.util.function.Supplier; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -28,6 +29,7 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.core.task.TaskExecutor; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.function.SingletonSupplier; /** * Bean post-processor that automatically applies asynchronous invocation @@ -75,14 +77,15 @@ public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAd protected final Log logger = LogFactory.getLog(getClass()); + @Nullable + private Supplier executor; + + @Nullable + private Supplier exceptionHandler; + @Nullable private Class asyncAnnotationType; - @Nullable - private Executor executor; - - @Nullable - private AsyncUncaughtExceptionHandler exceptionHandler; public AsyncAnnotationBeanPostProcessor() { @@ -90,6 +93,40 @@ public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAd } + /** + * Configure this post-processor with the given executor and exception handler suppliers, + * applying the corresponding default if a supplier is not resolvable. + * @since 5.1 + */ + public void configure( + @Nullable Supplier executor, @Nullable Supplier exceptionHandler) { + + this.executor = executor; + this.exceptionHandler = exceptionHandler; + } + + /** + * Set the {@link Executor} to use when invoking methods asynchronously. + *

If not specified, default executor resolution will apply: searching for a + * unique {@link TaskExecutor} bean in the context, or for an {@link Executor} + * bean named "taskExecutor" otherwise. If neither of the two is resolvable, + * a local default executor will be created within the interceptor. + * @see AnnotationAsyncExecutionInterceptor#getDefaultExecutor(BeanFactory) + * @see #DEFAULT_TASK_EXECUTOR_BEAN_NAME + */ + public void setExecutor(Executor executor) { + this.executor = SingletonSupplier.of(executor); + } + + /** + * Set the {@link AsyncUncaughtExceptionHandler} to use to handle uncaught + * exceptions thrown by asynchronous method executions. + * @since 4.1 + */ + public void setExceptionHandler(AsyncUncaughtExceptionHandler exceptionHandler) { + this.exceptionHandler = SingletonSupplier.of(exceptionHandler); + } + /** * Set the 'async' annotation type to be detected at either class or method * level. By default, both the {@link Async} annotation and the EJB 3.1 @@ -104,29 +141,6 @@ public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAd this.asyncAnnotationType = asyncAnnotationType; } - /** - * Set the {@link Executor} to use when invoking methods asynchronously. - *

If not specified, default executor resolution will apply: searching for a - * unique {@link TaskExecutor} bean in the context, or for an {@link Executor} - * bean named "taskExecutor" otherwise. If neither of the two is resolvable, - * a local default executor will be created within the interceptor. - * @see AsyncAnnotationAdvisor#AsyncAnnotationAdvisor(Executor, AsyncUncaughtExceptionHandler) - * @see AnnotationAsyncExecutionInterceptor#getDefaultExecutor(BeanFactory) - * @see #DEFAULT_TASK_EXECUTOR_BEAN_NAME - */ - public void setExecutor(Executor executor) { - this.executor = executor; - } - - /** - * Set the {@link AsyncUncaughtExceptionHandler} to use to handle uncaught - * exceptions thrown by asynchronous method executions. - * @since 4.1 - */ - public void setExceptionHandler(AsyncUncaughtExceptionHandler exceptionHandler) { - this.exceptionHandler = exceptionHandler; - } - @Override public void setBeanFactory(BeanFactory beanFactory) { diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/ProxyAsyncConfiguration.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/ProxyAsyncConfiguration.java index d85348f0d6..1e0768c48d 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/ProxyAsyncConfiguration.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/ProxyAsyncConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 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. @@ -32,6 +32,7 @@ import org.springframework.util.Assert; * * @author Chris Beams * @author Stephane Nicoll + * @author Juergen Hoeller * @since 3.1 * @see EnableAsync * @see AsyncConfigurationSelector @@ -45,16 +46,11 @@ public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration { public AsyncAnnotationBeanPostProcessor asyncAdvisor() { Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected"); AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor(); + bpp.configure(this.executor, this.exceptionHandler); Class customAsyncAnnotation = this.enableAsync.getClass("annotation"); if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) { bpp.setAsyncAnnotationType(customAsyncAnnotation); } - if (this.executor != null) { - bpp.setExecutor(this.executor); - } - if (this.exceptionHandler != null) { - bpp.setExceptionHandler(this.exceptionHandler); - } bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass")); bpp.setOrder(this.enableAsync.getNumber("order")); return bpp; diff --git a/spring-context/src/test/java/org/springframework/cache/config/EnableCachingTests.java b/spring-context/src/test/java/org/springframework/cache/config/EnableCachingTests.java index 75856f4167..a454d40daf 100644 --- a/spring-context/src/test/java/org/springframework/cache/config/EnableCachingTests.java +++ b/spring-context/src/test/java/org/springframework/cache/config/EnableCachingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -66,23 +66,21 @@ public class EnableCachingTests extends AbstractCacheAnnotationTests { } @Test - public void singleCacheManagerBean() throws Throwable { + public void singleCacheManagerBean() { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(SingleCacheManagerConfig.class); ctx.refresh(); } - @Test(expected = IllegalStateException.class) - public void multipleCacheManagerBeans() throws Throwable { + @Test + public void multipleCacheManagerBeans() { 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; + catch (IllegalStateException ex) { + assertTrue(ex.getMessage().contains("no unique bean of type CacheManager")); } } @@ -93,8 +91,8 @@ public class EnableCachingTests extends AbstractCacheAnnotationTests { ctx.refresh(); // does not throw an exception } - @Test(expected = IllegalStateException.class) - public void multipleCachingConfigurers() throws Throwable { + @Test + public void multipleCachingConfigurers() { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(MultiCacheManagerConfigurer.class, EnableCachingConfig.class); try { @@ -102,22 +100,20 @@ public class EnableCachingTests extends AbstractCacheAnnotationTests { } catch (BeanCreationException ex) { Throwable root = ex.getRootCause(); + assertTrue(root instanceof IllegalStateException); assertTrue(root.getMessage().contains("implementations of CachingConfigurer")); - throw root; } } - @Test(expected = IllegalStateException.class) - public void noCacheManagerBeans() throws Throwable { + @Test + public void noCacheManagerBeans() { 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; + catch (IllegalStateException ex) { + assertTrue(ex.getMessage().contains("no bean of type CacheManager")); } } diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableAsyncTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableAsyncTests.java index b0dc552349..ea0bbf9d96 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableAsyncTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableAsyncTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -33,16 +33,19 @@ import org.springframework.aop.Advisor; import org.springframework.aop.framework.Advised; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.aop.support.AopUtils; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.BeanNotOfRequiredTypeException; import org.springframework.beans.factory.UnsatisfiedDependencyException; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.config.BeanPostProcessor; 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.context.annotation.Lazy; import org.springframework.core.Ordered; +import org.springframework.lang.Nullable; import org.springframework.scheduling.concurrent.CustomizableThreadFactory; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; @@ -218,6 +221,29 @@ public class EnableAsyncTests { ctx.close(); } + @Test + public void customExecutorBeanConfig() throws InterruptedException { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(CustomExecutorBeanConfig.class, ExecutorPostProcessor.class); + ctx.refresh(); + + AsyncBean asyncBean = ctx.getBean(AsyncBean.class); + asyncBean.work(); + Thread.sleep(500); + assertThat(asyncBean.getThreadOfExecution().getName(), startsWith("Post-")); + + TestableAsyncUncaughtExceptionHandler exceptionHandler = (TestableAsyncUncaughtExceptionHandler) + ctx.getBean("exceptionHandler"); + assertFalse("handler should not have been called yet", exceptionHandler.isCalled()); + + asyncBean.fail(); + Thread.sleep(500); + Method method = ReflectionUtils.findMethod(AsyncBean.class, "fail"); + exceptionHandler.assertCalledWith(method, UnsupportedOperationException.class); + + ctx.close(); + } + @Test public void spr14949FindsOnInterfaceWithInterfaceProxy() throws InterruptedException { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Spr14949ConfigA.class); @@ -440,6 +466,53 @@ public class EnableAsyncTests { } + @Configuration + @EnableAsync + static class CustomExecutorBeanConfig implements AsyncConfigurer { + + @Bean + public AsyncBean asyncBean() { + return new AsyncBean(); + } + + @Override + public Executor getAsyncExecutor() { + return executor(); + } + + @Bean + public ThreadPoolTaskExecutor executor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setThreadNamePrefix("Custom-"); + executor.initialize(); + return executor; + } + + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return exceptionHandler(); + } + + @Bean + public AsyncUncaughtExceptionHandler exceptionHandler() { + return new TestableAsyncUncaughtExceptionHandler(); + } + } + + + public static class ExecutorPostProcessor implements BeanPostProcessor { + + @Nullable + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof ThreadPoolTaskExecutor) { + ((ThreadPoolTaskExecutor) bean).setThreadNamePrefix("Post-"); + } + return bean; + } + } + + public interface AsyncInterface { @Async diff --git a/spring-context/src/test/java/org/springframework/scheduling/config/AnnotationDrivenBeanDefinitionParserTests.java b/spring-context/src/test/java/org/springframework/scheduling/config/AnnotationDrivenBeanDefinitionParserTests.java index d9a997d1fb..09de4861be 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/config/AnnotationDrivenBeanDefinitionParserTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/config/AnnotationDrivenBeanDefinitionParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 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. @@ -16,6 +16,8 @@ package org.springframework.scheduling.config; +import java.util.function.Supplier; + import org.junit.Before; import org.junit.Test; @@ -55,7 +57,7 @@ public class AnnotationDrivenBeanDefinitionParserTests { public void asyncPostProcessorExecutorReference() { Object executor = context.getBean("testExecutor"); Object postProcessor = context.getBean(TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME); - assertSame(executor, new DirectFieldAccessor(postProcessor).getPropertyValue("executor")); + assertSame(executor, ((Supplier) new DirectFieldAccessor(postProcessor).getPropertyValue("executor")).get()); } @Test @@ -69,7 +71,7 @@ public class AnnotationDrivenBeanDefinitionParserTests { public void asyncPostProcessorExceptionHandlerReference() { Object exceptionHandler = context.getBean("testExceptionHandler"); Object postProcessor = context.getBean(TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME); - assertSame(exceptionHandler, new DirectFieldAccessor(postProcessor).getPropertyValue("exceptionHandler")); + assertSame(exceptionHandler, ((Supplier) new DirectFieldAccessor(postProcessor).getPropertyValue("exceptionHandler")).get()); } } diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/package-info.java b/spring-core/src/main/java/org/springframework/util/concurrent/package-info.java index 32a77878f9..fc4777eff0 100644 --- a/spring-core/src/main/java/org/springframework/util/concurrent/package-info.java +++ b/spring-core/src/main/java/org/springframework/util/concurrent/package-info.java @@ -1,5 +1,5 @@ /** - * Useful generic {@code java.util.concurrent.Future} extension. + * Useful generic {@code java.util.concurrent.Future} extensions. */ @NonNullApi @NonNullFields diff --git a/spring-core/src/main/java/org/springframework/util/function/SingletonSupplier.java b/spring-core/src/main/java/org/springframework/util/function/SingletonSupplier.java new file mode 100644 index 0000000000..db62277653 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/util/function/SingletonSupplier.java @@ -0,0 +1,159 @@ +/* + * Copyright 2002-2018 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.util.function; + +import java.util.function.Supplier; + +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * A {@link java.util.function Supplier} decorator that caches a singleton result and + * makes it available from {@link #get()} (nullable) and {@link #obtain()} (null-safe). + * + *

A {@code SingletonSupplier} can be constructed via {@code of} factory methods + * or via constructors that provide a default supplier as a fallback. This is + * particularly useful for method reference suppliers, falling back to a default + * supplier for a method that returned {@code null} and caching the result. + * + * @author Juergen Hoeller + * @since 5.1 + * @param the type of results supplied by this supplier + */ +public class SingletonSupplier implements Supplier { + + @Nullable + private final Supplier instanceSupplier; + + @Nullable + private final Supplier defaultSupplier; + + @Nullable + private volatile T singletonInstance; + + + /** + * Build a {@code SingletonSupplier} with the given singleton instance + * and a default supplier for the case when the instance is {@code null}. + * @param instance the singleton instance (potentially {@code null}) + * @param defaultSupplier the default supplier as a fallback + */ + public SingletonSupplier(@Nullable T instance, Supplier defaultSupplier) { + this.instanceSupplier = null; + this.defaultSupplier = defaultSupplier; + this.singletonInstance = instance; + } + + /** + * Build a {@code SingletonSupplier} with the given instance supplier + * and a default supplier for the case when the instance is {@code null}. + * @param instanceSupplier the immediate instance supplier + * @param defaultSupplier the default supplier as a fallback + */ + public SingletonSupplier(@Nullable Supplier instanceSupplier, Supplier defaultSupplier) { + this.instanceSupplier = instanceSupplier; + this.defaultSupplier = defaultSupplier; + } + + private SingletonSupplier(Supplier supplier) { + this.instanceSupplier = supplier; + this.defaultSupplier = null; + } + + private SingletonSupplier(T singletonInstance) { + this.instanceSupplier = null; + this.defaultSupplier = null; + this.singletonInstance = singletonInstance; + } + + + /** + * Get the shared singleton instance for this supplier. + * @return the singleton instance (or {@code null} if none) + */ + @Override + @Nullable + public T get() { + T instance = this.singletonInstance; + if (instance == null) { + synchronized (this) { + instance = this.singletonInstance; + if (instance == null) { + if (this.instanceSupplier != null) { + instance = this.instanceSupplier.get(); + } + if (instance == null && this.defaultSupplier != null) { + instance = this.defaultSupplier.get(); + } + this.singletonInstance = instance; + } + } + } + return instance; + } + + /** + * Obtain the shared singleton instance for this supplier. + * @return the singleton instance (never {@code null}) + * @throws IllegalStateException in case of no instance + */ + public T obtain() { + T instance = get(); + Assert.state(instance != null, "No instance from Supplier"); + return instance; + } + + + /** + * Build a {@code SingletonSupplier} with the given singleton instance. + * @param instance the singleton instance (never {@code null}) + * @return the singleton supplier (never {@code null}) + */ + public static SingletonSupplier of(T instance) { + return new SingletonSupplier<>(instance); + } + + /** + * Build a {@code SingletonSupplier} with the given singleton instance. + * @param instance the singleton instance (potentially {@code null}) + * @return the singleton supplier, or {@code null} if the instance was {@code null} + */ + @Nullable + public static SingletonSupplier ofNullable(@Nullable T instance) { + return (instance != null ? new SingletonSupplier<>(instance) : null); + } + + /** + * Build a {@code SingletonSupplier} with the given supplier. + * @param supplier the instance supplier (never {@code null}) + * @return the singleton supplier (never {@code null}) + */ + public static SingletonSupplier of(Supplier supplier) { + return new SingletonSupplier<>(supplier); + } + + /** + * Build a {@code SingletonSupplier} with the given supplier. + * @param supplier the instance supplier (potentially {@code null}) + * @return the singleton supplier, or {@code null} if the instance supplier was {@code null} + */ + @Nullable + public static SingletonSupplier ofNullable(@Nullable Supplier supplier) { + return (supplier != null ? new SingletonSupplier<>(supplier) : null); + } + +} diff --git a/spring-core/src/main/java/org/springframework/util/function/SupplierUtils.java b/spring-core/src/main/java/org/springframework/util/function/SupplierUtils.java new file mode 100644 index 0000000000..f801995ede --- /dev/null +++ b/spring-core/src/main/java/org/springframework/util/function/SupplierUtils.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2018 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.util.function; + +import java.util.function.Supplier; + +import org.springframework.lang.Nullable; + +/** + * Convenience utilities for {@link java.util.function.Supplier} handling. + * + * @author Juergen Hoeller + * @since 5.1 + * @see SingletonSupplier + */ +public abstract class SupplierUtils { + + /** + * Resolve the given {@code Supplier}, getting its result or immediately + * returning {@code null} if the supplier itself was {@code null}. + * @param supplier the supplier to resolve + * @return the supplier's result, or {@code null} if none + */ + @Nullable + public static T resolve(@Nullable Supplier supplier) { + return (supplier != null ? supplier.get() : null); + } + +} diff --git a/spring-core/src/main/java/org/springframework/util/function/package-info.java b/spring-core/src/main/java/org/springframework/util/function/package-info.java new file mode 100644 index 0000000000..7c16649bd9 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/util/function/package-info.java @@ -0,0 +1,9 @@ +/** + * Useful generic {@code java.util.function} helper classes. + */ +@NonNullApi +@NonNullFields +package org.springframework.util.function; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields;