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
This commit is contained in:
Juergen Hoeller
2018-07-14 19:29:32 +02:00
parent 680afa75d8
commit f6fdffd663
53 changed files with 785 additions and 330 deletions

View File

@@ -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"));
}
}

View File

@@ -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

View File

@@ -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());
}
}