18
18
19
19
import java .lang .reflect .Method ;
20
20
import java .util .HashMap ;
21
+ import java .util .LinkedHashSet ;
21
22
import java .util .Map ;
23
+ import java .util .Set ;
24
+ import java .util .concurrent .ConcurrentHashMap ;
22
25
import java .util .concurrent .ScheduledExecutorService ;
23
26
24
27
import org .springframework .aop .support .AopUtils ;
40
43
import org .springframework .util .Assert ;
41
44
import org .springframework .util .ReflectionUtils ;
42
45
import org .springframework .util .ReflectionUtils .MethodCallback ;
46
+ import org .springframework .util .StringUtils ;
43
47
import org .springframework .util .StringValueResolver ;
44
48
45
49
/**
@@ -78,7 +82,10 @@ public class ScheduledAnnotationBeanPostProcessor
78
82
79
83
private final ScheduledTaskRegistrar registrar = new ScheduledTaskRegistrar ();
80
84
85
+ private final Map <Class <?>, Boolean > nonAnnotatedClasses = new ConcurrentHashMap <Class <?>, Boolean >(64 );
81
86
87
+
88
+ @ Override
82
89
public int getOrder () {
83
90
return LOWEST_PRECEDENCE ;
84
91
}
@@ -127,12 +134,11 @@ else if (schedulers.size() == 1) {
127
134
this .registrar .setScheduler (schedulers .values ().iterator ().next ());
128
135
}
129
136
else if (schedulers .size () >= 2 ){
130
- throw new IllegalStateException (
131
- "More than one TaskScheduler and/or ScheduledExecutorService " +
132
- "exist within the context. Remove all but one of the beans; or " +
133
- "implement the SchedulingConfigurer interface and call " +
134
- "ScheduledTaskRegistrar#setScheduler explicitly within the " +
135
- "configureTasks() callback. Found the following beans: " + schedulers .keySet ());
137
+ throw new IllegalStateException ("More than one TaskScheduler and/or ScheduledExecutorService " +
138
+ "exist within the context. Remove all but one of the beans; or implement the " +
139
+ "SchedulingConfigurer interface and call ScheduledTaskRegistrar#setScheduler " +
140
+ "explicitly within the configureTasks() callback. Found the following beans: " +
141
+ schedulers .keySet ());
136
142
}
137
143
}
138
144
@@ -145,126 +151,144 @@ public Object postProcessBeforeInitialization(Object bean, String beanName) {
145
151
}
146
152
147
153
public Object postProcessAfterInitialization (final Object bean , String beanName ) {
148
- final Class <?> targetClass = AopUtils .getTargetClass (bean );
149
- ReflectionUtils .doWithMethods (targetClass , new MethodCallback () {
150
- public void doWith (Method method ) throws IllegalArgumentException , IllegalAccessException {
151
- Scheduled annotation = AnnotationUtils .getAnnotation (method , Scheduled .class );
152
- if (annotation != null ) {
153
- try {
154
- Assert .isTrue (void .class .equals (method .getReturnType ()),
155
- "Only void-returning methods may be annotated with @Scheduled" );
156
- Assert .isTrue (method .getParameterTypes ().length == 0 ,
157
- "Only no-arg methods may be annotated with @Scheduled" );
158
- if (AopUtils .isJdkDynamicProxy (bean )) {
159
- try {
160
- // found a @Scheduled method on the target class for this JDK proxy -> is it
161
- // also present on the proxy itself?
162
- method = bean .getClass ().getMethod (method .getName (), method .getParameterTypes ());
163
- }
164
- catch (SecurityException ex ) {
165
- ReflectionUtils .handleReflectionException (ex );
166
- }
167
- catch (NoSuchMethodException ex ) {
168
- throw new IllegalStateException (String .format (
169
- "@Scheduled method '%s' found on bean target class '%s', " +
170
- "but not found in any interface(s) for bean JDK proxy. Either " +
171
- "pull the method up to an interface or switch to subclass (CGLIB) " +
172
- "proxies by setting proxy-target-class/proxyTargetClass " +
173
- "attribute to 'true'" , method .getName (), targetClass .getSimpleName ()));
174
- }
175
- }
176
- Runnable runnable = new ScheduledMethodRunnable (bean , method );
177
- boolean processedSchedule = false ;
178
- String errorMessage = "Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required" ;
179
- // Determine initial delay
180
- long initialDelay = annotation .initialDelay ();
181
- String initialDelayString = annotation .initialDelayString ();
182
- if (!"" .equals (initialDelayString )) {
183
- Assert .isTrue (initialDelay < 0 , "Specify 'initialDelay' or 'initialDelayString', not both" );
184
- if (embeddedValueResolver != null ) {
185
- initialDelayString = embeddedValueResolver .resolveStringValue (initialDelayString );
186
- }
187
- try {
188
- initialDelay = Integer .parseInt (initialDelayString );
189
- }
190
- catch (NumberFormatException ex ) {
191
- throw new IllegalArgumentException (
192
- "Invalid initialDelayString value \" " + initialDelayString + "\" - cannot parse into integer" );
193
- }
194
- }
195
- // Check cron expression
196
- String cron = annotation .cron ();
197
- if (!"" .equals (cron )) {
198
- Assert .isTrue (initialDelay == -1 , "'initialDelay' not supported for cron triggers" );
199
- processedSchedule = true ;
200
- if (embeddedValueResolver != null ) {
201
- cron = embeddedValueResolver .resolveStringValue (cron );
202
- }
203
- registrar .addCronTask (new CronTask (runnable , cron ));
204
- }
205
- // At this point we don't need to differentiate between initial delay set or not anymore
206
- if (initialDelay < 0 ) {
207
- initialDelay = 0 ;
208
- }
209
- // Check fixed delay
210
- long fixedDelay = annotation .fixedDelay ();
211
- if (fixedDelay >= 0 ) {
212
- Assert .isTrue (!processedSchedule , errorMessage );
213
- processedSchedule = true ;
214
- registrar .addFixedDelayTask (new IntervalTask (runnable , fixedDelay , initialDelay ));
215
- }
216
- String fixedDelayString = annotation .fixedDelayString ();
217
- if (!"" .equals (fixedDelayString )) {
218
- Assert .isTrue (!processedSchedule , errorMessage );
219
- processedSchedule = true ;
220
- if (embeddedValueResolver != null ) {
221
- fixedDelayString = embeddedValueResolver .resolveStringValue (fixedDelayString );
222
- }
223
- try {
224
- fixedDelay = Integer .parseInt (fixedDelayString );
225
- }
226
- catch (NumberFormatException ex ) {
227
- throw new IllegalArgumentException (
228
- "Invalid fixedDelayString value \" " + fixedDelayString + "\" - cannot parse into integer" );
229
- }
230
- registrar .addFixedDelayTask (new IntervalTask (runnable , fixedDelay , initialDelay ));
231
- }
232
- // Check fixed rate
233
- long fixedRate = annotation .fixedRate ();
234
- if (fixedRate >= 0 ) {
235
- Assert .isTrue (!processedSchedule , errorMessage );
236
- processedSchedule = true ;
237
- registrar .addFixedRateTask (new IntervalTask (runnable , fixedRate , initialDelay ));
238
- }
239
- String fixedRateString = annotation .fixedRateString ();
240
- if (!"" .equals (fixedRateString )) {
241
- Assert .isTrue (!processedSchedule , errorMessage );
242
- processedSchedule = true ;
243
- if (embeddedValueResolver != null ) {
244
- fixedRateString = embeddedValueResolver .resolveStringValue (fixedRateString );
245
- }
246
- try {
247
- fixedRate = Integer .parseInt (fixedRateString );
248
- }
249
- catch (NumberFormatException ex ) {
250
- throw new IllegalArgumentException (
251
- "Invalid fixedRateString value \" " + fixedRateString + "\" - cannot parse into integer" );
252
- }
253
- registrar .addFixedRateTask (new IntervalTask (runnable , fixedRate , initialDelay ));
254
- }
255
- // Check whether we had any attribute set
256
- Assert .isTrue (processedSchedule , errorMessage );
257
- }
258
- catch (IllegalArgumentException ex ) {
259
- throw new IllegalStateException (
260
- "Encountered invalid @Scheduled method '" + method .getName () + "': " + ex .getMessage ());
154
+ if (!this .nonAnnotatedClasses .containsKey (bean .getClass ())) {
155
+ final Set <Method > annotatedMethods = new LinkedHashSet <Method >(1 );
156
+ Class <?> targetClass = AopUtils .getTargetClass (bean );
157
+ ReflectionUtils .doWithMethods (targetClass , new MethodCallback () {
158
+ public void doWith (Method method ) throws IllegalArgumentException , IllegalAccessException {
159
+ Scheduled scheduled = AnnotationUtils .getAnnotation (method , Scheduled .class );
160
+ if (scheduled != null ) {
161
+ processScheduled (scheduled , method , bean );
162
+ annotatedMethods .add (method );
261
163
}
262
164
}
165
+ });
166
+ if (annotatedMethods .isEmpty ()) {
167
+ this .nonAnnotatedClasses .put (bean .getClass (), Boolean .TRUE );
263
168
}
264
- });
169
+ }
265
170
return bean ;
266
171
}
267
172
173
+ private void processScheduled (Scheduled scheduled , Method method , Object bean ) {
174
+ try {
175
+ Assert .isTrue (void .class .equals (method .getReturnType ()),
176
+ "Only void-returning methods may be annotated with @Scheduled" );
177
+ Assert .isTrue (method .getParameterTypes ().length == 0 ,
178
+ "Only no-arg methods may be annotated with @Scheduled" );
179
+
180
+ if (AopUtils .isJdkDynamicProxy (bean )) {
181
+ try {
182
+ // Found a @Scheduled method on the target class for this JDK proxy ->
183
+ // is it also present on the proxy itself?
184
+ method = bean .getClass ().getMethod (method .getName (), method .getParameterTypes ());
185
+ }
186
+ catch (SecurityException ex ) {
187
+ ReflectionUtils .handleReflectionException (ex );
188
+ }
189
+ catch (NoSuchMethodException ex ) {
190
+ throw new IllegalStateException (String .format (
191
+ "@Scheduled method '%s' found on bean target class '%s', " +
192
+ "but not found in any interface(s) for bean JDK proxy. Either " +
193
+ "pull the method up to an interface or switch to subclass (CGLIB) " +
194
+ "proxies by setting proxy-target-class/proxyTargetClass " +
195
+ "attribute to 'true'" , method .getName (), method .getDeclaringClass ().getSimpleName ()));
196
+ }
197
+ }
198
+
199
+ Runnable runnable = new ScheduledMethodRunnable (bean , method );
200
+ boolean processedSchedule = false ;
201
+ String errorMessage = "Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required" ;
202
+
203
+ // Determine initial delay
204
+ long initialDelay = scheduled .initialDelay ();
205
+ String initialDelayString = scheduled .initialDelayString ();
206
+ if (StringUtils .hasText (initialDelayString )) {
207
+ Assert .isTrue (initialDelay < 0 , "Specify 'initialDelay' or 'initialDelayString', not both" );
208
+ if (this .embeddedValueResolver != null ) {
209
+ initialDelayString = this .embeddedValueResolver .resolveStringValue (initialDelayString );
210
+ }
211
+ try {
212
+ initialDelay = Integer .parseInt (initialDelayString );
213
+ }
214
+ catch (NumberFormatException ex ) {
215
+ throw new IllegalArgumentException (
216
+ "Invalid initialDelayString value \" " + initialDelayString + "\" - cannot parse into integer" );
217
+ }
218
+ }
219
+
220
+ // Check cron expression
221
+ String cron = scheduled .cron ();
222
+ if (StringUtils .hasText (cron )) {
223
+ Assert .isTrue (initialDelay == -1 , "'initialDelay' not supported for cron triggers" );
224
+ processedSchedule = true ;
225
+ if (this .embeddedValueResolver != null ) {
226
+ cron = this .embeddedValueResolver .resolveStringValue (cron );
227
+ }
228
+ this .registrar .addCronTask (new CronTask (runnable , cron ));
229
+ }
230
+ // At this point we don't need to differentiate between initial delay set or not anymore
231
+ if (initialDelay < 0 ) {
232
+ initialDelay = 0 ;
233
+ }
234
+
235
+ // Check fixed delay
236
+ long fixedDelay = scheduled .fixedDelay ();
237
+ if (fixedDelay >= 0 ) {
238
+ Assert .isTrue (!processedSchedule , errorMessage );
239
+ processedSchedule = true ;
240
+ this .registrar .addFixedDelayTask (new IntervalTask (runnable , fixedDelay , initialDelay ));
241
+ }
242
+ String fixedDelayString = scheduled .fixedDelayString ();
243
+ if (StringUtils .hasText (fixedDelayString )) {
244
+ Assert .isTrue (!processedSchedule , errorMessage );
245
+ processedSchedule = true ;
246
+ if (this .embeddedValueResolver != null ) {
247
+ fixedDelayString = this .embeddedValueResolver .resolveStringValue (fixedDelayString );
248
+ }
249
+ try {
250
+ fixedDelay = Integer .parseInt (fixedDelayString );
251
+ }
252
+ catch (NumberFormatException ex ) {
253
+ throw new IllegalArgumentException (
254
+ "Invalid fixedDelayString value \" " + fixedDelayString + "\" - cannot parse into integer" );
255
+ }
256
+ this .registrar .addFixedDelayTask (new IntervalTask (runnable , fixedDelay , initialDelay ));
257
+ }
258
+
259
+ // Check fixed rate
260
+ long fixedRate = scheduled .fixedRate ();
261
+ if (fixedRate >= 0 ) {
262
+ Assert .isTrue (!processedSchedule , errorMessage );
263
+ processedSchedule = true ;
264
+ this .registrar .addFixedRateTask (new IntervalTask (runnable , fixedRate , initialDelay ));
265
+ }
266
+ String fixedRateString = scheduled .fixedRateString ();
267
+ if (StringUtils .hasText (fixedRateString )) {
268
+ Assert .isTrue (!processedSchedule , errorMessage );
269
+ processedSchedule = true ;
270
+ if (this .embeddedValueResolver != null ) {
271
+ fixedRateString = this .embeddedValueResolver .resolveStringValue (fixedRateString );
272
+ }
273
+ try {
274
+ fixedRate = Integer .parseInt (fixedRateString );
275
+ }
276
+ catch (NumberFormatException ex ) {
277
+ throw new IllegalArgumentException (
278
+ "Invalid fixedRateString value \" " + fixedRateString + "\" - cannot parse into integer" );
279
+ }
280
+ this .registrar .addFixedRateTask (new IntervalTask (runnable , fixedRate , initialDelay ));
281
+ }
282
+
283
+ // Check whether we had any attribute set
284
+ Assert .isTrue (processedSchedule , errorMessage );
285
+ }
286
+ catch (IllegalArgumentException ex ) {
287
+ throw new IllegalStateException (
288
+ "Encountered invalid @Scheduled method '" + method .getName () + "': " + ex .getMessage ());
289
+ }
290
+ }
291
+
268
292
269
293
public void destroy () throws Exception {
270
294
this .registrar .destroy ();
0 commit comments