1 /* 2 * Copyright 2002-2011 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.springframework.scheduling.annotation; 18 19 import static org.easymock.EasyMock.createMock; 20 import static org.easymock.EasyMock.replay; 21 import static org.hamcrest.CoreMatchers.is; 22 import static org.hamcrest.Matchers.greaterThan; 23 import static org.junit.Assert.assertThat; 24 import static org.junit.Assert.assertTrue; 25 import static org.junit.Assert.fail; 26 27 import java.util.concurrent.atomic.AtomicInteger; 28 29 import org.junit.Test; 30 import org.springframework.aop.support.AopUtils; 31 import org.springframework.beans.factory.BeanCreationException; 32 import org.springframework.context.annotation.AnnotationConfigApplicationContext; 33 import org.springframework.context.annotation.Bean; 34 import org.springframework.context.annotation.Configuration; 35 import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor; 36 import org.springframework.dao.support.PersistenceExceptionTranslator; 37 import org.springframework.scheduling.annotation.EnableScheduling; 38 import org.springframework.stereotype.Repository; 39 import org.springframework.transaction.CallCountingTransactionManager; 40 import org.springframework.transaction.PlatformTransactionManager; 41 import org.springframework.transaction.annotation.EnableTransactionManagement; 42 import org.springframework.transaction.annotation.Transactional; 43 44 /** 45 * Integration tests cornering bug SPR-8651, which revealed that @Scheduled methods may 46 * not work well with beans that have already been proxied for other reasons such 47 * as @Transactional or @Async processing. 48 * 49 * @author Chris Beams 50 * @since 3.1 51 */ 52 public class ScheduledAndTransactionalAnnotationIntegrationTests { 53 54 @Test failsWhenJdkProxyAndScheduledMethodNotPresentOnInterface()55 public void failsWhenJdkProxyAndScheduledMethodNotPresentOnInterface() { 56 AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); 57 ctx.register(Config.class, JdkProxyTxConfig.class, RepoConfigA.class); 58 try { 59 ctx.refresh(); 60 fail("expected exception"); 61 } catch (BeanCreationException ex) { 62 assertTrue(ex.getRootCause().getMessage().startsWith("@Scheduled method 'scheduled' found")); 63 } 64 } 65 66 @Test succeedsWhenSubclassProxyAndScheduledMethodNotPresentOnInterface()67 public void succeedsWhenSubclassProxyAndScheduledMethodNotPresentOnInterface() throws InterruptedException { 68 AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); 69 ctx.register(Config.class, SubclassProxyTxConfig.class, RepoConfigA.class); 70 ctx.refresh(); 71 72 Thread.sleep(10); // allow @Scheduled method to be called several times 73 74 MyRepository repository = ctx.getBean(MyRepository.class); 75 CallCountingTransactionManager txManager = ctx.getBean(CallCountingTransactionManager.class); 76 assertThat("repository is not a proxy", AopUtils.isAopProxy(repository), is(true)); 77 assertThat("@Scheduled method never called", repository.getInvocationCount(), greaterThan(0)); 78 assertThat("no transactions were committed", txManager.commits, greaterThan(0)); 79 } 80 81 @Test succeedsWhenJdkProxyAndScheduledMethodIsPresentOnInterface()82 public void succeedsWhenJdkProxyAndScheduledMethodIsPresentOnInterface() throws InterruptedException { 83 AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); 84 ctx.register(Config.class, JdkProxyTxConfig.class, RepoConfigB.class); 85 ctx.refresh(); 86 87 Thread.sleep(10); // allow @Scheduled method to be called several times 88 89 MyRepositoryWithScheduledMethod repository = ctx.getBean(MyRepositoryWithScheduledMethod.class); 90 CallCountingTransactionManager txManager = ctx.getBean(CallCountingTransactionManager.class); 91 assertThat("repository is not a proxy", AopUtils.isAopProxy(repository), is(true)); 92 assertThat("@Scheduled method never called", repository.getInvocationCount(), greaterThan(0)); 93 assertThat("no transactions were committed", txManager.commits, greaterThan(0)); 94 } 95 96 @Configuration 97 @EnableTransactionManagement 98 static class JdkProxyTxConfig { } 99 100 @Configuration 101 @EnableTransactionManagement(proxyTargetClass=true) 102 static class SubclassProxyTxConfig { } 103 104 @Configuration 105 static class RepoConfigA { 106 @Bean repository()107 public MyRepository repository() { 108 return new MyRepositoryImpl(); 109 } 110 } 111 112 @Configuration 113 static class RepoConfigB { 114 @Bean repository()115 public MyRepositoryWithScheduledMethod repository() { 116 return new MyRepositoryWithScheduledMethodImpl(); 117 } 118 } 119 120 @Configuration 121 @EnableScheduling 122 static class Config { 123 124 @Bean peTranslationPostProcessor()125 public PersistenceExceptionTranslationPostProcessor peTranslationPostProcessor() { 126 return new PersistenceExceptionTranslationPostProcessor(); 127 } 128 129 @Bean txManager()130 public PlatformTransactionManager txManager() { 131 return new CallCountingTransactionManager(); 132 } 133 134 @Bean peTranslator()135 public PersistenceExceptionTranslator peTranslator() { 136 PersistenceExceptionTranslator txlator = createMock(PersistenceExceptionTranslator.class); 137 replay(txlator); 138 return txlator; 139 } 140 } 141 142 public interface MyRepository { getInvocationCount()143 int getInvocationCount(); 144 } 145 146 @Repository 147 static class MyRepositoryImpl implements MyRepository { 148 149 private final AtomicInteger count = new AtomicInteger(0); 150 151 @Transactional 152 @Scheduled(fixedDelay = 5) scheduled()153 public void scheduled() { 154 this.count.incrementAndGet(); 155 } 156 getInvocationCount()157 public int getInvocationCount() { 158 return this.count.get(); 159 } 160 } 161 162 public interface MyRepositoryWithScheduledMethod { getInvocationCount()163 int getInvocationCount(); scheduled()164 public void scheduled(); 165 } 166 167 @Repository 168 static class MyRepositoryWithScheduledMethodImpl implements MyRepositoryWithScheduledMethod { 169 170 private final AtomicInteger count = new AtomicInteger(0); 171 172 @Transactional 173 @Scheduled(fixedDelay = 5) scheduled()174 public void scheduled() { 175 this.count.incrementAndGet(); 176 } 177 getInvocationCount()178 public int getInvocationCount() { 179 return this.count.get(); 180 } 181 } 182 }