1
16
17 package org.springframework.retry.annotation;
18
19 import java.lang.annotation.Annotation;
20 import java.lang.reflect.Method;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.concurrent.atomic.AtomicBoolean;
26
27 import org.aopalliance.intercept.MethodInterceptor;
28 import org.aopalliance.intercept.MethodInvocation;
29
30 import org.springframework.aop.IntroductionInterceptor;
31 import org.springframework.beans.BeansException;
32 import org.springframework.beans.factory.BeanFactory;
33 import org.springframework.beans.factory.BeanFactoryAware;
34 import org.springframework.beans.factory.config.ConfigurableBeanFactory;
35 import org.springframework.context.expression.BeanFactoryResolver;
36 import org.springframework.core.annotation.AnnotationAwareOrderComparator;
37 import org.springframework.core.annotation.AnnotationUtils;
38 import org.springframework.expression.common.TemplateParserContext;
39 import org.springframework.expression.spel.standard.SpelExpressionParser;
40 import org.springframework.expression.spel.support.StandardEvaluationContext;
41 import org.springframework.retry.RetryListener;
42 import org.springframework.retry.RetryPolicy;
43 import org.springframework.retry.backoff.BackOffPolicy;
44 import org.springframework.retry.backoff.ExponentialBackOffPolicy;
45 import org.springframework.retry.backoff.ExponentialRandomBackOffPolicy;
46 import org.springframework.retry.backoff.FixedBackOffPolicy;
47 import org.springframework.retry.backoff.NoBackOffPolicy;
48 import org.springframework.retry.backoff.Sleeper;
49 import org.springframework.retry.backoff.UniformRandomBackOffPolicy;
50 import org.springframework.retry.interceptor.FixedKeyGenerator;
51 import org.springframework.retry.interceptor.MethodArgumentsKeyGenerator;
52 import org.springframework.retry.interceptor.MethodInvocationRecoverer;
53 import org.springframework.retry.interceptor.NewMethodArgumentsIdentifier;
54 import org.springframework.retry.interceptor.RetryInterceptorBuilder;
55 import org.springframework.retry.policy.CircuitBreakerRetryPolicy;
56 import org.springframework.retry.policy.ExpressionRetryPolicy;
57 import org.springframework.retry.policy.MapRetryContextCache;
58 import org.springframework.retry.policy.RetryContextCache;
59 import org.springframework.retry.policy.SimpleRetryPolicy;
60 import org.springframework.retry.support.RetryTemplate;
61 import org.springframework.util.ReflectionUtils;
62 import org.springframework.util.ReflectionUtils.MethodCallback;
63 import org.springframework.util.StringUtils;
64
65
75 public class AnnotationAwareRetryOperationsInterceptor implements IntroductionInterceptor, BeanFactoryAware {
76
77 private static final TemplateParserContext PARSER_CONTEXT = new TemplateParserContext();
78
79 private static final SpelExpressionParser PARSER = new SpelExpressionParser();
80
81 private final StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
82
83 private final Map<Object, Map<Method, MethodInterceptor>> delegates =
84 new HashMap<Object, Map<Method, MethodInterceptor>>();
85
86 private RetryContextCache retryContextCache = new MapRetryContextCache();
87
88 private MethodArgumentsKeyGenerator methodArgumentsKeyGenerator;
89
90 private NewMethodArgumentsIdentifier newMethodArgumentsIdentifier;
91
92 private Sleeper sleeper;
93
94 private BeanFactory beanFactory;
95
96 private RetryListener[] globalListeners;
97
98
101 public void setSleeper(Sleeper sleeper) {
102 this.sleeper = sleeper;
103 }
104
105
110 public void setRetryContextCache(RetryContextCache retryContextCache) {
111 this.retryContextCache = retryContextCache;
112 }
113
114
117 public void setKeyGenerator(MethodArgumentsKeyGenerator methodArgumentsKeyGenerator) {
118 this.methodArgumentsKeyGenerator = methodArgumentsKeyGenerator;
119 }
120
121
124 public void setNewItemIdentifier(NewMethodArgumentsIdentifier newMethodArgumentsIdentifier) {
125 this.newMethodArgumentsIdentifier = newMethodArgumentsIdentifier;
126 }
127
128
132 public void setListeners(Collection<RetryListener> globalListeners) {
133 ArrayList<RetryListener> retryListeners = new ArrayList<RetryListener>(globalListeners);
134 AnnotationAwareOrderComparator.sort(retryListeners);
135 this.globalListeners = retryListeners.toArray(new RetryListener[0]);
136 }
137
138 @Override
139 public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
140 this.beanFactory = beanFactory;
141 this.evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
142 }
143
144 @Override
145 public boolean implementsInterface(Class<?> intf) {
146 return org.springframework.retry.interceptor.Retryable.class.isAssignableFrom(intf);
147 }
148
149 @Override
150 public Object invoke(MethodInvocation invocation) throws Throwable {
151 MethodInterceptor delegate = getDelegate(invocation.getThis(), invocation.getMethod());
152 if (delegate != null) {
153 return delegate.invoke(invocation);
154 }
155 else {
156 return invocation.proceed();
157 }
158 }
159
160 private MethodInterceptor getDelegate(Object target, Method method) {
161 if (!this.delegates.containsKey(target) || !this.delegates.get(target).containsKey(method)) {
162 synchronized (this.delegates) {
163 if (!this.delegates.containsKey(target)) {
164 this.delegates.put(target, new HashMap<Method, MethodInterceptor>());
165 }
166 Map<Method, MethodInterceptor> delegatesForTarget = this.delegates.get(target);
167 if (!delegatesForTarget.containsKey(method)) {
168 Retryable retryable = AnnotationUtils.findAnnotation(method, Retryable.class);
169 if (retryable == null) {
170 retryable = AnnotationUtils.findAnnotation(method.getDeclaringClass(), Retryable.class);
171 }
172 if (retryable == null) {
173 retryable = findAnnotationOnTarget(target, method);
174 }
175 if (retryable == null) {
176 return delegatesForTarget.put(method, null);
177 }
178 MethodInterceptor delegate;
179 if (StringUtils.hasText(retryable.interceptor())) {
180 delegate = this.beanFactory.getBean(retryable.interceptor(), MethodInterceptor.class);
181 }
182 else if (retryable.stateful()) {
183 delegate = getStatefulInterceptor(target, method, retryable);
184 }
185 else {
186 delegate = getStatelessInterceptor(target, method, retryable);
187 }
188 delegatesForTarget.put(method, delegate);
189 }
190 }
191 }
192 return this.delegates.get(target).get(method);
193 }
194
195 private Retryable findAnnotationOnTarget(Object target, Method method) {
196 try {
197 Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
198 Retryable retryable = AnnotationUtils.findAnnotation(targetMethod, Retryable.class);
199 if (retryable == null) {
200 retryable = AnnotationUtils.findAnnotation(targetMethod.getDeclaringClass(), Retryable.class);
201 }
202
203 return retryable;
204 }
205 catch (Exception e) {
206 return null;
207 }
208 }
209
210 private MethodInterceptor getStatelessInterceptor(Object target, Method method, Retryable retryable) {
211 RetryTemplate template = createTemplate(retryable.listeners());
212 template.setRetryPolicy(getRetryPolicy(retryable));
213 template.setBackOffPolicy(getBackoffPolicy(retryable.backoff()));
214 return RetryInterceptorBuilder.stateless()
215 .retryOperations(template)
216 .label(retryable.label())
217 .recoverer(getRecoverer(target, method))
218 .build();
219 }
220
221 private MethodInterceptor getStatefulInterceptor(Object target, Method method, Retryable retryable) {
222 RetryTemplate template = createTemplate(retryable.listeners());
223 template.setRetryContextCache(this.retryContextCache);
224
225 CircuitBreaker circuit = AnnotationUtils.findAnnotation(method, CircuitBreaker.class);
226 if (circuit!=null) {
227 RetryPolicy policy = getRetryPolicy(circuit);
228 CircuitBreakerRetryPolicy breaker = new CircuitBreakerRetryPolicy(policy);
229 breaker.setOpenTimeout(getOpenTimeout(circuit));
230 breaker.setResetTimeout(getResetTimeout(circuit));
231 template.setRetryPolicy(breaker);
232 template.setBackOffPolicy(new NoBackOffPolicy());
233 String label = circuit.label();
234 if (!StringUtils.hasText(label)) {
235 label = method.toGenericString();
236 }
237 return RetryInterceptorBuilder.circuitBreaker()
238 .keyGenerator(new FixedKeyGenerator("circuit"))
239 .retryOperations(template)
240 .recoverer(getRecoverer(target, method))
241 .label(label)
242 .build();
243 }
244 RetryPolicy policy = getRetryPolicy(retryable);
245 template.setRetryPolicy(policy);
246 template.setBackOffPolicy(getBackoffPolicy(retryable.backoff()));
247 String label = retryable.label();
248 return RetryInterceptorBuilder.stateful()
249 .keyGenerator(this.methodArgumentsKeyGenerator)
250 .newMethodArgumentsIdentifier(this.newMethodArgumentsIdentifier)
251 .retryOperations(template)
252 .label(label)
253 .recoverer(getRecoverer(target, method))
254 .build();
255 }
256
257 private long getOpenTimeout(CircuitBreaker circuit) {
258 if (StringUtils.hasText(circuit.openTimeoutExpression())) {
259 Long value = PARSER.parseExpression(resolve(circuit.openTimeoutExpression()), PARSER_CONTEXT)
260 .getValue(Long.class);
261 if (value != null) {
262 return value;
263 }
264 }
265 return circuit.openTimeout();
266 }
267
268 private long getResetTimeout(CircuitBreaker circuit) {
269 if (StringUtils.hasText(circuit.resetTimeoutExpression())) {
270 Long value = PARSER.parseExpression(resolve(circuit.resetTimeoutExpression()), PARSER_CONTEXT)
271 .getValue(Long.class);
272 if (value != null) {
273 return value;
274 }
275 }
276 return circuit.resetTimeout();
277 }
278
279 private RetryTemplate createTemplate(String[] listenersBeanNames) {
280 RetryTemplate template = new RetryTemplate();
281 if (listenersBeanNames.length > 0) {
282 template.setListeners(getListenersBeans(listenersBeanNames));
283 } else if (globalListeners !=null) {
284 template.setListeners(globalListeners);
285 }
286 return template;
287 }
288
289 private RetryListener[] getListenersBeans(String[] listenersBeanNames) {
290 RetryListener[] listeners = new RetryListener[listenersBeanNames.length];
291 for (int i = 0; i < listeners.length; i++) {
292 listeners[i] = beanFactory.getBean(listenersBeanNames[i], RetryListener.class);
293 }
294 return listeners;
295 }
296
297 private MethodInvocationRecoverer<?> getRecoverer(Object target, Method method) {
298 if (target instanceof MethodInvocationRecoverer) {
299 return (MethodInvocationRecoverer<?>) target;
300 }
301 final AtomicBoolean foundRecoverable = new AtomicBoolean(false);
302 ReflectionUtils.doWithMethods(target.getClass(), new MethodCallback() {
303 @Override
304 public void doWith(Method method) throws IllegalArgumentException,
305 IllegalAccessException {
306 if (AnnotationUtils.findAnnotation(method, Recover.class) != null) {
307 foundRecoverable.set(true);
308 }
309 }
310 });
311
312 if (!foundRecoverable.get()) {
313 return null;
314 }
315 return new RecoverAnnotationRecoveryHandler<Object>(target, method);
316 }
317
318 private RetryPolicy getRetryPolicy(Annotation retryable) {
319 Map<String, Object> attrs = AnnotationUtils.getAnnotationAttributes(retryable);
320 @SuppressWarnings("unchecked")
321 Class<? extends Throwable>[] includes = (Class<? extends Throwable>[]) attrs.get("value");
322 String exceptionExpression = (String) attrs.get("exceptionExpression");
323 boolean hasExpression = StringUtils.hasText(exceptionExpression);
324 if (includes.length == 0) {
325 @SuppressWarnings("unchecked")
326 Class<? extends Throwable>[] value = (Class<? extends Throwable>[]) attrs.get("include");
327 includes = value;
328 }
329 @SuppressWarnings("unchecked")
330 Class<? extends Throwable>[] excludes = (Class<? extends Throwable>[]) attrs.get("exclude");
331 Integer maxAttempts = (Integer) attrs.get("maxAttempts");
332 String maxAttemptsExpression = (String) attrs.get("maxAttemptsExpression");
333 if (StringUtils.hasText(maxAttemptsExpression)) {
334 maxAttempts = PARSER.parseExpression(resolve(maxAttemptsExpression), PARSER_CONTEXT)
335 .getValue(this.evaluationContext, Integer.class);
336 }
337 if (includes.length == 0 && excludes.length == 0) {
338 SimpleRetryPolicy simple = hasExpression ? new ExpressionRetryPolicy(resolve(exceptionExpression))
339 .withBeanFactory(this.beanFactory)
340 : new SimpleRetryPolicy();
341 simple.setMaxAttempts(maxAttempts);
342 return simple;
343 }
344 Map<Class<? extends Throwable>, Boolean> policyMap = new HashMap<Class<? extends Throwable>, Boolean>();
345 for (Class<? extends Throwable> type : includes) {
346 policyMap.put(type, true);
347 }
348 for (Class<? extends Throwable> type : excludes) {
349 policyMap.put(type, false);
350 }
351 boolean retryNotExcluded = includes.length == 0;
352 if (hasExpression) {
353 return new ExpressionRetryPolicy(maxAttempts, policyMap, true, exceptionExpression, retryNotExcluded)
354 .withBeanFactory(this.beanFactory);
355 }
356 else {
357 return new SimpleRetryPolicy(maxAttempts, policyMap, true, retryNotExcluded);
358 }
359 }
360
361 private BackOffPolicy getBackoffPolicy(Backoff backoff) {
362 long min = backoff.delay() == 0 ? backoff.value() : backoff.delay();
363 if (StringUtils.hasText(backoff.delayExpression())) {
364 min = PARSER.parseExpression(resolve(backoff.delayExpression()), PARSER_CONTEXT)
365 .getValue(this.evaluationContext, Long.class);
366 }
367 long max = backoff.maxDelay();
368 if (StringUtils.hasText(backoff.maxDelayExpression())) {
369 max = PARSER.parseExpression(resolve(backoff.maxDelayExpression()), PARSER_CONTEXT)
370 .getValue(this.evaluationContext, Long.class);
371 }
372 double multiplier = backoff.multiplier();
373 if (StringUtils.hasText(backoff.multiplierExpression())) {
374 multiplier = PARSER.parseExpression(resolve(backoff.multiplierExpression()), PARSER_CONTEXT)
375 .getValue(this.evaluationContext, Double.class);
376 }
377 if (multiplier > 0) {
378 ExponentialBackOffPolicy policy = new ExponentialBackOffPolicy();
379 if (backoff.random()) {
380 policy = new ExponentialRandomBackOffPolicy();
381 }
382 policy.setInitialInterval(min);
383 policy.setMultiplier(multiplier);
384 policy.setMaxInterval(max > min ? max : ExponentialBackOffPolicy.DEFAULT_MAX_INTERVAL);
385 if (this.sleeper != null) {
386 policy.setSleeper(this.sleeper);
387 }
388 return policy;
389 }
390 if (max > min) {
391 UniformRandomBackOffPolicy policy = new UniformRandomBackOffPolicy();
392 policy.setMinBackOffPeriod(min);
393 policy.setMaxBackOffPeriod(max);
394 if (this.sleeper != null) {
395 policy.setSleeper(this.sleeper);
396 }
397 return policy;
398 }
399 FixedBackOffPolicy policy = new FixedBackOffPolicy();
400 policy.setBackOffPeriod(min);
401 if (this.sleeper != null) {
402 policy.setSleeper(this.sleeper);
403 }
404 return policy;
405 }
406
407
412 private String resolve(String value) {
413 if (this.beanFactory != null && this.beanFactory instanceof ConfigurableBeanFactory) {
414 return ((ConfigurableBeanFactory) this.beanFactory).resolveEmbeddedValue(value);
415 }
416 return value;
417 }
418
419 }
420