/* * Copyright 2002-2015 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.scheduling.annotation; import java.lang.reflect.Method; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.junit.Test; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.aop.support.AopUtils; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.GenericXmlApplicationContext; import org.springframework.context.support.StaticApplicationContext; import org.springframework.core.io.ClassPathResource; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.util.ReflectionUtils; import org.springframework.util.concurrent.ListenableFuture; import static org.junit.Assert.*; /** * @author Mark Fisher * @author Juergen Hoeller * @author Stephane Nicoll */ public class AsyncAnnotationBeanPostProcessorTests { @Test public void proxyCreated() { ConfigurableApplicationContext context = initContext( new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class)); Object target = context.getBean("target"); assertTrue(AopUtils.isAopProxy(target)); context.close(); } @Test public void invokedAsynchronously() { ConfigurableApplicationContext context = initContext( new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class)); ITestBean testBean = context.getBean("target", ITestBean.class); testBean.test(); Thread mainThread = Thread.currentThread(); testBean.await(3000); Thread asyncThread = testBean.getThread(); assertNotSame(mainThread, asyncThread); context.close(); } @Test public void threadNamePrefix() { BeanDefinition processorDefinition = new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class); ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setThreadNamePrefix("testExecutor"); executor.afterPropertiesSet(); processorDefinition.getPropertyValues().add("executor", executor); ConfigurableApplicationContext context = initContext(processorDefinition); ITestBean testBean = context.getBean("target", ITestBean.class); testBean.test(); testBean.await(3000); Thread asyncThread = testBean.getThread(); assertTrue(asyncThread.getName().startsWith("testExecutor")); context.close(); } @Test public void taskExecutorByBeanType() { StaticApplicationContext context = new StaticApplicationContext(); BeanDefinition processorDefinition = new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class); context.registerBeanDefinition("postProcessor", processorDefinition); BeanDefinition executorDefinition = new RootBeanDefinition(ThreadPoolTaskExecutor.class); executorDefinition.getPropertyValues().add("threadNamePrefix", "testExecutor"); context.registerBeanDefinition("myExecutor", executorDefinition); BeanDefinition targetDefinition = new RootBeanDefinition(AsyncAnnotationBeanPostProcessorTests.TestBean.class); context.registerBeanDefinition("target", targetDefinition); context.refresh(); ITestBean testBean = context.getBean("target", ITestBean.class); testBean.test(); testBean.await(3000); Thread asyncThread = testBean.getThread(); assertTrue(asyncThread.getName().startsWith("testExecutor")); context.close(); } @Test public void taskExecutorByBeanName() { StaticApplicationContext context = new StaticApplicationContext(); BeanDefinition processorDefinition = new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class); context.registerBeanDefinition("postProcessor", processorDefinition); BeanDefinition executorDefinition = new RootBeanDefinition(ThreadPoolTaskExecutor.class); executorDefinition.getPropertyValues().add("threadNamePrefix", "testExecutor"); context.registerBeanDefinition("myExecutor", executorDefinition); BeanDefinition executorDefinition2 = new RootBeanDefinition(ThreadPoolTaskExecutor.class); executorDefinition2.getPropertyValues().add("threadNamePrefix", "testExecutor2"); context.registerBeanDefinition("taskExecutor", executorDefinition2); BeanDefinition targetDefinition = new RootBeanDefinition(AsyncAnnotationBeanPostProcessorTests.TestBean.class); context.registerBeanDefinition("target", targetDefinition); context.refresh(); ITestBean testBean = context.getBean("target", ITestBean.class); testBean.test(); testBean.await(3000); Thread asyncThread = testBean.getThread(); assertTrue(asyncThread.getName().startsWith("testExecutor2")); context.close(); } @Test public void configuredThroughNamespace() { GenericXmlApplicationContext context = new GenericXmlApplicationContext(); context.load(new ClassPathResource("taskNamespaceTests.xml", getClass())); context.refresh(); ITestBean testBean = context.getBean("target", ITestBean.class); testBean.test(); testBean.await(3000); Thread asyncThread = testBean.getThread(); assertTrue(asyncThread.getName().startsWith("testExecutor")); TestableAsyncUncaughtExceptionHandler exceptionHandler = context.getBean("exceptionHandler", TestableAsyncUncaughtExceptionHandler.class); assertFalse("handler should not have been called yet", exceptionHandler.isCalled()); testBean.failWithVoid(); exceptionHandler.await(3000); Method m = ReflectionUtils.findMethod(TestBean.class, "failWithVoid"); exceptionHandler.assertCalledWith(m, UnsupportedOperationException.class); context.close(); } @Test public void handleExceptionWithFuture() { ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(ConfigWithExceptionHandler.class); ITestBean testBean = context.getBean("target", ITestBean.class); TestableAsyncUncaughtExceptionHandler exceptionHandler = context.getBean("exceptionHandler", TestableAsyncUncaughtExceptionHandler.class); assertFalse("handler should not have been called yet", exceptionHandler.isCalled()); Future<Object> result = testBean.failWithFuture(); assertFutureWithException(result, exceptionHandler); } @Test public void handleExceptionWithListenableFuture() { ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(ConfigWithExceptionHandler.class); ITestBean testBean = context.getBean("target", ITestBean.class); TestableAsyncUncaughtExceptionHandler exceptionHandler = context.getBean("exceptionHandler", TestableAsyncUncaughtExceptionHandler.class); assertFalse("handler should not have been called yet", exceptionHandler.isCalled()); Future<Object> result = testBean.failWithListenableFuture(); assertFutureWithException(result, exceptionHandler); } private void assertFutureWithException(Future<Object> result, TestableAsyncUncaughtExceptionHandler exceptionHandler) { try { result.get(); } catch (InterruptedException ex) { fail("Should not have failed with InterruptedException: " + ex); } catch (ExecutionException ex) { // expected assertEquals("Wrong exception cause", UnsupportedOperationException.class, ex.getCause().getClass()); } assertFalse("handler should never be called with Future return type", exceptionHandler.isCalled()); } @Test public void handleExceptionWithCustomExceptionHandler() { Method m = ReflectionUtils.findMethod(TestBean.class, "failWithVoid"); TestableAsyncUncaughtExceptionHandler exceptionHandler = new TestableAsyncUncaughtExceptionHandler(); BeanDefinition processorDefinition = new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class); processorDefinition.getPropertyValues().add("exceptionHandler", exceptionHandler); ConfigurableApplicationContext context = initContext(processorDefinition); ITestBean testBean = context.getBean("target", ITestBean.class); assertFalse("Handler should not have been called", exceptionHandler.isCalled()); testBean.failWithVoid(); exceptionHandler.await(3000); exceptionHandler.assertCalledWith(m, UnsupportedOperationException.class); } @Test public void exceptionHandlerThrowsUnexpectedException() { Method m = ReflectionUtils.findMethod(TestBean.class, "failWithVoid"); TestableAsyncUncaughtExceptionHandler exceptionHandler = new TestableAsyncUncaughtExceptionHandler(true); BeanDefinition processorDefinition = new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class); processorDefinition.getPropertyValues().add("exceptionHandler", exceptionHandler); processorDefinition.getPropertyValues().add("executor", new DirectExecutor()); ConfigurableApplicationContext context = initContext(processorDefinition); ITestBean testBean = context.getBean("target", ITestBean.class); assertFalse("Handler should not have been called", exceptionHandler.isCalled()); try { testBean.failWithVoid(); exceptionHandler.assertCalledWith(m, UnsupportedOperationException.class); } catch (Exception e) { fail("No unexpected exception should have been received"); } } private ConfigurableApplicationContext initContext(BeanDefinition asyncAnnotationBeanPostProcessorDefinition) { StaticApplicationContext context = new StaticApplicationContext(); BeanDefinition targetDefinition = new RootBeanDefinition(AsyncAnnotationBeanPostProcessorTests.TestBean.class); context.registerBeanDefinition("postProcessor", asyncAnnotationBeanPostProcessorDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); return context; } private interface ITestBean { Thread getThread(); void test(); Future<Object> failWithFuture(); ListenableFuture<Object> failWithListenableFuture(); void failWithVoid(); void await(long timeout); } public static class TestBean implements ITestBean { private Thread thread; private final CountDownLatch latch = new CountDownLatch(1); @Override public Thread getThread() { return this.thread; } @Override @Async public void test() { this.thread = Thread.currentThread(); this.latch.countDown(); } @Async @Override public Future<Object> failWithFuture() { throw new UnsupportedOperationException("failWithFuture"); } @Async @Override public ListenableFuture<Object> failWithListenableFuture() { throw new UnsupportedOperationException("failWithListenableFuture"); } @Async @Override public void failWithVoid() { throw new UnsupportedOperationException("failWithVoid"); } @Override public void await(long timeout) { try { this.latch.await(timeout, TimeUnit.MILLISECONDS); } catch (Exception e) { Thread.currentThread().interrupt(); } } } private static class DirectExecutor implements Executor { @Override public void execute(Runnable r) { r.run(); } } @Configuration @EnableAsync static class ConfigWithExceptionHandler extends AsyncConfigurerSupport { @Bean public ITestBean target() { return new TestBean(); } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return exceptionHandler(); } @Bean public TestableAsyncUncaughtExceptionHandler exceptionHandler() { return new TestableAsyncUncaughtExceptionHandler(); } } }