From ce3cf3251d8bc01f688b1f1abccc4ffaa85db120 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 28 Feb 2017 13:11:15 +0100 Subject: [PATCH] Tests for annotation lookups in interfaces (currently ignored for CGLIB proxies) Issue: SPR-15271 Issue: SPR-14949 Issue: SPR-14322 (cherry picked from commit d003f66) --- .../aspectj/TransactionAspectTests.java | 76 +++++----- .../cache/CacheReproTests.java | 98 +++++++++++- .../annotation/EnableAsyncTests.java | 130 ++++++++++++++-- .../EnableTransactionManagementTests.java | 141 ++++++++++++++---- 4 files changed, 363 insertions(+), 82 deletions(-) diff --git a/spring-aspects/src/test/java/org/springframework/transaction/aspectj/TransactionAspectTests.java b/spring-aspects/src/test/java/org/springframework/transaction/aspectj/TransactionAspectTests.java index 5971b0ae00..c12bf12049 100644 --- a/spring-aspects/src/test/java/org/springframework/transaction/aspectj/TransactionAspectTests.java +++ b/spring-aspects/src/test/java/org/springframework/transaction/aspectj/TransactionAspectTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2017 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,55 +18,45 @@ package org.springframework.transaction.aspectj; import java.lang.reflect.Method; +import org.junit.Before; +import org.junit.Test; + import org.springframework.tests.transaction.CallCountingTransactionManager; import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource; -import org.springframework.transaction.interceptor.TransactionAspectSupport; import org.springframework.transaction.interceptor.TransactionAttribute; +import static org.junit.Assert.*; + /** * @author Rod Johnson * @author Ramnivas Laddad * @author Juergen Hoeller * @author Sam Brannen */ -@SuppressWarnings("deprecation") -public class TransactionAspectTests extends org.springframework.test.AbstractDependencyInjectionSpringContextTests { +public class TransactionAspectTests { - private CallCountingTransactionManager txManager; + private final CallCountingTransactionManager txManager = new CallCountingTransactionManager(); - private TransactionalAnnotationOnlyOnClassWithNoInterface annotationOnlyOnClassWithNoInterface; + private final TransactionalAnnotationOnlyOnClassWithNoInterface annotationOnlyOnClassWithNoInterface = + new TransactionalAnnotationOnlyOnClassWithNoInterface(); - private ClassWithProtectedAnnotatedMember beanWithAnnotatedProtectedMethod; + private final ClassWithProtectedAnnotatedMember beanWithAnnotatedProtectedMethod = + new ClassWithProtectedAnnotatedMember(); - private ClassWithPrivateAnnotatedMember beanWithAnnotatedPrivateMethod; + private final ClassWithPrivateAnnotatedMember beanWithAnnotatedPrivateMethod = + new ClassWithPrivateAnnotatedMember(); - private MethodAnnotationOnClassWithNoInterface methodAnnotationOnly = new MethodAnnotationOnClassWithNoInterface(); + private final MethodAnnotationOnClassWithNoInterface methodAnnotationOnly = + new MethodAnnotationOnClassWithNoInterface(); - public void setAnnotationOnlyOnClassWithNoInterface( - TransactionalAnnotationOnlyOnClassWithNoInterface annotationOnlyOnClassWithNoInterface) { - this.annotationOnlyOnClassWithNoInterface = annotationOnlyOnClassWithNoInterface; - } - - public void setClassWithAnnotatedProtectedMethod(ClassWithProtectedAnnotatedMember aBean) { - this.beanWithAnnotatedProtectedMethod = aBean; - } - - public void setClassWithAnnotatedPrivateMethod(ClassWithPrivateAnnotatedMember aBean) { - this.beanWithAnnotatedPrivateMethod = aBean; - } - - public void setTransactionAspect(TransactionAspectSupport transactionAspect) { - this.txManager = (CallCountingTransactionManager) transactionAspect.getTransactionManager(); - } - - - @Override - protected String[] getConfigPaths() { - return new String[] { "TransactionAspectTests-context.xml" }; + @Before + public void initContext() { + AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager); } + @Test public void testCommitOnAnnotatedClass() throws Throwable { txManager.clear(); assertEquals(0, txManager.begun); @@ -74,6 +64,7 @@ public class TransactionAspectTests extends org.springframework.test.AbstractDep assertEquals(1, txManager.commits); } + @Test public void testCommitOnAnnotatedProtectedMethod() throws Throwable { txManager.clear(); assertEquals(0, txManager.begun); @@ -81,6 +72,7 @@ public class TransactionAspectTests extends org.springframework.test.AbstractDep assertEquals(1, txManager.commits); } + @Test public void testCommitOnAnnotatedPrivateMethod() throws Throwable { txManager.clear(); assertEquals(0, txManager.begun); @@ -88,6 +80,7 @@ public class TransactionAspectTests extends org.springframework.test.AbstractDep assertEquals(1, txManager.commits); } + @Test public void testNoCommitOnNonAnnotatedNonPublicMethodInTransactionalType() throws Throwable { txManager.clear(); assertEquals(0,txManager.begun); @@ -95,6 +88,7 @@ public class TransactionAspectTests extends org.springframework.test.AbstractDep assertEquals(0,txManager.begun); } + @Test public void testCommitOnAnnotatedMethod() throws Throwable { txManager.clear(); assertEquals(0, txManager.begun); @@ -102,6 +96,7 @@ public class TransactionAspectTests extends org.springframework.test.AbstractDep assertEquals(1, txManager.commits); } + @Test public void testNotTransactional() throws Throwable { txManager.clear(); assertEquals(0, txManager.begun); @@ -109,6 +104,7 @@ public class TransactionAspectTests extends org.springframework.test.AbstractDep assertEquals(0, txManager.begun); } + @Test public void testDefaultCommitOnAnnotatedClass() throws Throwable { final Exception ex = new Exception(); try { @@ -125,6 +121,7 @@ public class TransactionAspectTests extends org.springframework.test.AbstractDep } } + @Test public void testDefaultRollbackOnAnnotatedClass() throws Throwable { final RuntimeException ex = new RuntimeException(); try { @@ -141,10 +138,11 @@ public class TransactionAspectTests extends org.springframework.test.AbstractDep } } + @Test public void testDefaultCommitOnSubclassOfAnnotatedClass() throws Throwable { final Exception ex = new Exception(); try { - testRollback(new TransactionOperationCallback() { + testRollback(new TransactionOperationCallback() { @Override public Object performTransactionalOperation() throws Throwable { return new SubclassOfClassWithTransactionalAnnotation().echo(ex); @@ -157,6 +155,7 @@ public class TransactionAspectTests extends org.springframework.test.AbstractDep } } + @Test public void testDefaultCommitOnSubclassOfClassWithTransactionalMethodAnnotated() throws Throwable { final Exception ex = new Exception(); try { @@ -173,6 +172,7 @@ public class TransactionAspectTests extends org.springframework.test.AbstractDep } } + @Test public void testDefaultCommitOnImplementationOfAnnotatedInterface() throws Throwable { final Exception ex = new Exception(); testNotTransactional(new TransactionOperationCallback() { @@ -185,16 +185,19 @@ public class TransactionAspectTests extends org.springframework.test.AbstractDep /** * Note: resolution does not occur. Thus we can't make a class transactional if - * it implements a transactionally annotated interface. This behaviour could only + * it implements a transactionally annotated interface. This behavior could only * be changed in AbstractFallbackTransactionAttributeSource in Spring proper. + * See SPR-14322. */ + @Test public void testDoesNotResolveTxAnnotationOnMethodFromClassImplementingAnnotatedInterface() throws Exception { AnnotationTransactionAttributeSource atas = new AnnotationTransactionAttributeSource(); - Method m = ImplementsAnnotatedInterface.class.getMethod("echo", Throwable.class); - TransactionAttribute ta = atas.getTransactionAttribute(m, ImplementsAnnotatedInterface.class); + Method method = ImplementsAnnotatedInterface.class.getMethod("echo", Throwable.class); + TransactionAttribute ta = atas.getTransactionAttribute(method, ImplementsAnnotatedInterface.class); assertNull(ta); } + @Test public void testDefaultRollbackOnImplementationOfAnnotatedInterface() throws Throwable { final Exception rollbackProvokingException = new RuntimeException(); testNotTransactional(new TransactionOperationCallback() { @@ -237,15 +240,19 @@ public class TransactionAspectTests extends org.springframework.test.AbstractDep private interface TransactionOperationCallback { + Object performTransactionalOperation() throws Throwable; } + public static class SubclassOfClassWithTransactionalAnnotation extends TransactionalAnnotationOnlyOnClassWithNoInterface { } + public static class SubclassOfClassWithTransactionalMethodAnnotation extends MethodAnnotationOnClassWithNoInterface { } + public static class ImplementsAnnotatedInterface implements ITransactional { @Override @@ -257,6 +264,7 @@ public class TransactionAspectTests extends org.springframework.test.AbstractDep } } + public static class NotTransactional { public void noop() { diff --git a/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java b/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java index edbffcf620..24495bdb1d 100644 --- a/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java +++ b/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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,12 +16,12 @@ package org.springframework.cache; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -58,8 +58,9 @@ public class CacheReproTests { @Rule public final ExpectedException thrown = ExpectedException.none(); + @Test - public void spr11124() throws Exception { + public void spr11124MultipleAnnotations() throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Spr11124Config.class); Spr11124Service bean = context.getBean(Spr11124Service.class); bean.single(2); @@ -70,7 +71,7 @@ public class CacheReproTests { } @Test - public void spr11249() throws Exception { + public void spr11249PrimitiveVarargs() throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Spr11249Config.class); Spr11249Service bean = context.getBean(Spr11249Service.class); Object result = bean.doSomething("op", 2, 3); @@ -128,8 +129,8 @@ public class CacheReproTests { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Spr13081Config.class); Spr13081Service bean = context.getBean(Spr13081Service.class); - thrown.expect(IllegalStateException.class); - thrown.expectMessage(MyCacheResolver.class.getName()); + this.thrown.expect(IllegalStateException.class); + this.thrown.expectMessage(MyCacheResolver.class.getName()); bean.getSimple(null); } @@ -167,6 +168,31 @@ public class CacheReproTests { assertSame(tb2, cache.get("tb1").get()); } + @Test + public void spr15271FindsOnInterfaceWithInterfaceProxy() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Spr15271ConfigA.class); + Spr15271Interface bean = context.getBean(Spr15271Interface.class); + Cache cache = context.getBean(CacheManager.class).getCache("itemCache"); + + TestBean tb = new TestBean("tb1"); + bean.insertItem(tb); + assertSame(tb, bean.findById("tb1").get()); + assertSame(tb, cache.get("tb1").get()); + } + + @Test @Ignore // TODO + public void spr15271FindsOnInterfaceWithCglibProxy() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Spr15271ConfigB.class); + Spr15271Interface bean = context.getBean(Spr15271Interface.class); + Cache cache = context.getBean(CacheManager.class).getCache("itemCache"); + + TestBean tb = new TestBean("tb1"); + bean.insertItem(tb); + assertSame(tb, bean.findById("tb1").get()); + assertSame(tb, cache.get("tb1").get()); + } + + @Configuration @EnableCaching public static class Spr11124Config { @@ -251,7 +277,7 @@ public class CacheReproTests { @Bean public CacheManager cacheManager() { SimpleCacheManager cacheManager = new SimpleCacheManager(); - cacheManager.setCaches(Arrays.asList(cache())); + cacheManager.setCaches(Collections.singletonList(cache())); return cacheManager; } @@ -358,6 +384,7 @@ public class CacheReproTests { } } + public static class Spr14853Service { @Cacheable(value = "itemCache", sync = true) @@ -372,6 +399,7 @@ public class CacheReproTests { } + @Configuration @EnableCaching public static class Spr14853Config { @@ -387,4 +415,60 @@ public class CacheReproTests { } } + + public interface Spr15271Interface { + + @Cacheable(value = "itemCache", sync = true) + Optional findById(String id); + + @CachePut(cacheNames = "itemCache", key = "#item.name") + TestBean insertItem(TestBean item); + } + + + public static class Spr15271Service implements Spr15271Interface { + + @Override + public Optional findById(String id) { + return Optional.of(new TestBean(id)); + } + + @Override + public TestBean insertItem(TestBean item) { + return item; + } + } + + + @Configuration + @EnableCaching + public static class Spr15271ConfigA { + + @Bean + public CacheManager cacheManager() { + return new ConcurrentMapCacheManager(); + } + + @Bean + public Spr15271Interface service() { + return new Spr15271Service(); + } + } + + + @Configuration + @EnableCaching(proxyTargetClass = true) + public static class Spr15271ConfigB { + + @Bean + public CacheManager cacheManager() { + return new ConcurrentMapCacheManager(); + } + + @Bean + public Spr15271Interface service() { + return new Spr15271Service(); + } + } + } 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 5fa835f40d..3e5cf952f8 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-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -25,6 +25,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Future; +import org.junit.Ignore; import org.junit.Test; import org.mockito.Mockito; @@ -196,8 +197,32 @@ public class EnableAsyncTests { asyncBean.fail(); Thread.sleep(500); - Method m = ReflectionUtils.findMethod(AsyncBean.class, "fail"); - exceptionHandler.assertCalledWith(m, UnsupportedOperationException.class); + 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); + + AsyncInterface asyncBean = ctx.getBean(AsyncInterface.class); + asyncBean.work(); + Thread.sleep(500); + assertThat(asyncBean.getThreadOfExecution().getName(), startsWith("Custom-")); + + ctx.close(); + } + + @Test @Ignore // TODO + public void spr14949FindsOnInterfaceWithCglibProxy() throws InterruptedException { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Spr14949ConfigB.class); + + AsyncInterface asyncBean = ctx.getBean(AsyncInterface.class); + asyncBean.work(); + Thread.sleep(500); + assertThat(asyncBean.getThreadOfExecution().getName(), startsWith("Custom-")); ctx.close(); } @@ -207,22 +232,22 @@ public class EnableAsyncTests { @Async public Future work0() { - return new AsyncResult(Thread.currentThread()); + return new AsyncResult<>(Thread.currentThread()); } @Async("e1") public Future work() { - return new AsyncResult(Thread.currentThread()); + return new AsyncResult<>(Thread.currentThread()); } @Async("otherExecutor") public Future work2() { - return new AsyncResult(Thread.currentThread()); + return new AsyncResult<>(Thread.currentThread()); } @Async("e2") public Future work3() { - return new AsyncResult(Thread.currentThread()); + return new AsyncResult<>(Thread.currentThread()); } } @@ -333,6 +358,28 @@ public class EnableAsyncTests { } + @Configuration + @EnableAsync + static class AsyncWithExecutorQualifiedByNameConfig { + + @Bean + public AsyncBeanWithExecutorQualifiedByName asyncBean() { + return new AsyncBeanWithExecutorQualifiedByName(); + } + + @Bean + public Executor e1() { + return new ThreadPoolTaskExecutor(); + } + + @Bean + @Qualifier("e2") + public Executor otherExecutor() { + return new ThreadPoolTaskExecutor(); + } + } + + @Configuration @EnableAsync static class CustomExecutorAsyncConfig implements AsyncConfigurer { @@ -362,24 +409,75 @@ public class EnableAsyncTests { } + public interface AsyncInterface { + + @Async + void work(); + + Thread getThreadOfExecution(); + } + + + public static class AsyncService implements AsyncInterface { + + private Thread threadOfExecution; + + @Override + public void work() { + this.threadOfExecution = Thread.currentThread(); + } + + @Override + public Thread getThreadOfExecution() { + return threadOfExecution; + } + } + + @Configuration @EnableAsync - static class AsyncWithExecutorQualifiedByNameConfig { + static class Spr14949ConfigA implements AsyncConfigurer { @Bean - public AsyncBeanWithExecutorQualifiedByName asyncBean() { - return new AsyncBeanWithExecutorQualifiedByName(); + public AsyncInterface asyncBean() { + return new AsyncService(); } - @Bean - public Executor e1() { - return new ThreadPoolTaskExecutor(); + @Override + public Executor getAsyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setThreadNamePrefix("Custom-"); + executor.initialize(); + return executor; } + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return null; + } + } + + + @Configuration + @EnableAsync(proxyTargetClass = true) + static class Spr14949ConfigB implements AsyncConfigurer { + @Bean - @Qualifier("e2") - public Executor otherExecutor() { - return new ThreadPoolTaskExecutor(); + public AsyncInterface asyncBean() { + return new AsyncService(); + } + + @Override + public Executor getAsyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setThreadNamePrefix("Custom-"); + executor.initialize(); + return executor; + } + + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return null; } } diff --git a/spring-tx/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementTests.java b/spring-tx/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementTests.java index 4e8e323adc..915c7ad056 100644 --- a/spring-tx/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementTests.java +++ b/spring-tx/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2017 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,10 +16,10 @@ package org.springframework.transaction.annotation; +import java.util.Collection; import java.util.Map; -import javax.annotation.PostConstruct; - +import org.junit.Ignore; import org.junit.Test; import org.springframework.aop.support.AopUtils; @@ -36,7 +36,6 @@ import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.stereotype.Service; import org.springframework.tests.transaction.CallCountingTransactionManager; import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.annotation.AnnotationTransactionNamespaceHandlerTests.TransactionalTestBean; import org.springframework.transaction.config.TransactionManagementConfigUtils; import org.springframework.transaction.event.TransactionalEventListenerFactory; @@ -47,6 +46,7 @@ import static org.junit.Assert.*; * Tests demonstrating use of @EnableTransactionManagement @Configuration classes. * * @author Chris Beams + * @author Juergen Hoeller * @author Stephane Nicoll * @author Sam Brannen * @since 3.1 @@ -134,7 +134,7 @@ public class EnableTransactionManagementTests { } @Test - public void spr11915() { + public void spr11915TransactionManagerAsManualSingleton() { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Spr11915Config.class); TransactionalTestBean bean = ctx.getBean(TransactionalTestBean.class); CallCountingTransactionManager txManager = ctx.getBean("qualifiedTransactionManager", CallCountingTransactionManager.class); @@ -152,6 +152,52 @@ public class EnableTransactionManagementTests { ctx.close(); } + @Test + public void spr14322FindsOnInterfaceWithInterfaceProxy() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Spr14322ConfigA.class); + TransactionalTestInterface bean = ctx.getBean(TransactionalTestInterface.class); + CallCountingTransactionManager txManager = ctx.getBean(CallCountingTransactionManager.class); + + bean.saveFoo(); + assertThat(txManager.begun, equalTo(1)); + assertThat(txManager.commits, equalTo(1)); + assertThat(txManager.rollbacks, equalTo(0)); + + ctx.close(); + } + + @Test @Ignore // TODO + public void spr14322FindsOnInterfaceWithCglibProxy() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Spr14322ConfigB.class); + TransactionalTestInterface bean = ctx.getBean(TransactionalTestInterface.class); + CallCountingTransactionManager txManager = ctx.getBean(CallCountingTransactionManager.class); + + bean.saveFoo(); + assertThat(txManager.begun, equalTo(1)); + assertThat(txManager.commits, equalTo(1)); + assertThat(txManager.rollbacks, equalTo(0)); + + ctx.close(); + } + + + @Service + public static class TransactionalTestBean { + + @Transactional(readOnly = true) + public Collection findAllFoos() { + return null; + } + + @Transactional("qualifiedTransactionManager") + public void saveQualifiedFoo() { + } + + @Transactional(transactionManager = "qualifiedTransactionManager") + public void saveQualifiedFooWithAttributeAlias() { + } + } + @Configuration @EnableTransactionManagement @@ -206,26 +252,6 @@ public class EnableTransactionManagementTests { } - @Configuration - @EnableTransactionManagement - static class Spr11915Config { - - @Autowired - private ConfigurableApplicationContext applicationContext; - - @PostConstruct - public void initializeApp() { - applicationContext.getBeanFactory().registerSingleton( - "qualifiedTransactionManager", new CallCountingTransactionManager()); - } - - @Bean - public TransactionalTestBean testBean() { - return new TransactionalTestBean(); - } - } - - @Configuration static class TxManagerConfig { @@ -255,4 +281,69 @@ public class EnableTransactionManagementTests { } } + + @Configuration + @EnableTransactionManagement + static class Spr11915Config { + + @Autowired + public void initializeApp(ConfigurableApplicationContext applicationContext) { + applicationContext.getBeanFactory().registerSingleton( + "qualifiedTransactionManager", new CallCountingTransactionManager()); + } + + @Bean + public TransactionalTestBean testBean() { + return new TransactionalTestBean(); + } + } + + + public interface TransactionalTestInterface { + + @Transactional + void saveFoo(); + } + + + @Service + public static class TransactionalTestService implements TransactionalTestInterface { + + @Override + public void saveFoo() { + } + } + + + @Configuration + @EnableTransactionManagement + static class Spr14322ConfigA { + + @Bean + public TransactionalTestInterface testBean() { + return new TransactionalTestService(); + } + + @Bean + public PlatformTransactionManager txManager() { + return new CallCountingTransactionManager(); + } + } + + + @Configuration + @EnableTransactionManagement(proxyTargetClass = true) + static class Spr14322ConfigB { + + @Bean + public TransactionalTestInterface testBean() { + return new TransactionalTestService(); + } + + @Bean + public PlatformTransactionManager txManager() { + return new CallCountingTransactionManager(); + } + } + }