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
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
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
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
107
+ public MyRepository repository () {
108
+ return new MyRepositoryImpl ();
109
+ }
110
+ }
111
+
112
+ @ Configuration
113
+ static class RepoConfigB {
114
+ @ Bean
115
+ public MyRepositoryWithScheduledMethod repository () {
116
+ return new MyRepositoryWithScheduledMethodImpl ();
117
+ }
118
+ }
119
+
120
+ @ Configuration
121
+ @ EnableScheduling
122
+ static class Config {
123
+
124
+ @ Bean
125
+ public PersistenceExceptionTranslationPostProcessor peTranslationPostProcessor () {
126
+ return new PersistenceExceptionTranslationPostProcessor ();
127
+ }
128
+
129
+ @ Bean
130
+ public PlatformTransactionManager txManager () {
131
+ return new CallCountingTransactionManager ();
132
+ }
133
+
134
+ @ Bean
135
+ public PersistenceExceptionTranslator peTranslator () {
136
+ PersistenceExceptionTranslator txlator = createMock (PersistenceExceptionTranslator .class );
137
+ replay (txlator );
138
+ return txlator ;
139
+ }
140
+ }
141
+
142
+ public interface MyRepository {
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 )
153
+ public void scheduled () {
154
+ this .count .incrementAndGet ();
155
+ }
156
+
157
+ public int getInvocationCount () {
158
+ return this .count .get ();
159
+ }
160
+ }
161
+
162
+ public interface MyRepositoryWithScheduledMethod {
163
+ int getInvocationCount ();
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 )
174
+ public void scheduled () {
175
+ this .count .incrementAndGet ();
176
+ }
177
+
178
+ public int getInvocationCount () {
179
+ return this .count .get ();
180
+ }
181
+ }
182
+ }
0 commit comments