Один из последователей моего блога отправляет электронное письмо с просьбой показать пример использования RealWorld в Spring AOP . Он упомянул, что в большинстве примеров использование Spring AOP демонстрируется для регистрации входа / выхода метода, управления транзакциями или проверки безопасности .
Он хотел узнать, как Spring AOP используется в «Реальном проекте для реальных проблем» . Поэтому я хотел бы показать, как я использовал Spring AOP для одного из моих проектов для решения реальной проблемы.
Мы не столкнемся с какими-то проблемами на этапах разработки, а узнаем только во время нагрузочного тестирования или только в производственных средах.
Например:
- Сбои удаленного вызова WebService из-за проблем с задержкой в сети
- Сбои запросов к базе данных из-за исключений блокировки и т. Д.
В большинстве случаев достаточно просто повторить одну и ту же операцию, чтобы устранить подобные ошибки.
Давайте посмотрим, как мы можем использовать Spring AOP для автоматической повторной попытки выполнения метода в случае возникновения какого-либо исключения. Мы можем использовать Spring AOP @Around advice для создания прокси для тех объектов, методы которых необходимо повторить, и реализовать логику повторения в Aspect .
Прежде чем приступить к реализации этих Spring Advice и Aspect, сначала давайте напишем простую утилиту для выполнения «Задачи», которая автоматически повторяется N раз, игнорируя данный набор Исключений.
|
1
2
3
|
public interface Task<T> { T execute();} |
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
import java.util.HashSet;import java.util.Set;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class TaskExecutionUtil { private static Logger logger = LoggerFactory.getLogger(TaskExecutionUtil.class); @SafeVarargs public static <T> T execute(Task<T> task, int noOfRetryAttempts, long sleepInterval, Class<? extends Throwable>... ignoreExceptions) { if (noOfRetryAttempts < 1) { noOfRetryAttempts = 1; } Set<Class<? extends Throwable>> ignoreExceptionsSet = new HashSet<Class<? extends Throwable>>(); if (ignoreExceptions != null && ignoreExceptions.length > 0) { for (Class<? extends Throwable> ignoreException : ignoreExceptions) { ignoreExceptionsSet.add(ignoreException); } } logger.debug("noOfRetryAttempts = "+noOfRetryAttempts); logger.debug("ignoreExceptionsSet = "+ignoreExceptionsSet); T result = null; for (int retryCount = 1; retryCount <= noOfRetryAttempts; retryCount++) { logger.debug("Executing the task. Attemp#"+retryCount); try { result = task.execute(); break; } catch (RuntimeException t) { Throwable e = t.getCause(); logger.error(" Caught Exception class"+e.getClass()); for (Class<? extends Throwable> ignoreExceptionClazz : ignoreExceptionsSet) { logger.error(" Comparing with Ignorable Exception : "+ignoreExceptionClazz.getName()); if (!ignoreExceptionClazz.isAssignableFrom(e.getClass())) { logger.error("Encountered exception which is not ignorable: "+e.getClass()); logger.error("Throwing exception to the caller"); throw t; } } logger.error("Failed at Retry attempt :" + retryCount + " of : " + noOfRetryAttempts); if (retryCount >= noOfRetryAttempts) { logger.error("Maximum retrial attempts exceeded."); logger.error("Throwing exception to the caller"); throw t; } try { Thread.sleep(sleepInterval); } catch (InterruptedException e1) { //Intentionally left blank } } } return result; }} |
Я надеюсь, что этот метод говорит сам за себя. Он принимает задачу и повторяет noOfRetryAttempts раз в случае, если метод task.execute () генерирует любое исключение, а ignoreExceptions указывает, какой тип исключений следует игнорировать при повторной попытке.
Теперь давайте создадим аннотацию Retry следующим образом:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface Retry { public int retryAttempts() default 3; public long sleepInterval() default 1000L; //milliseconds Class<? extends Throwable>[] ignoreExceptions() default { RuntimeException.class }; } |
Мы будем использовать эту аннотацию @Retry, чтобы определить, какие методы необходимо повторить.
Теперь давайте реализуем Аспект, который применяется к методу с аннотацией @Retry .
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
import java.lang.reflect.Method;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.reflect.MethodSignature;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;@Component@Aspectpublic class MethodRetryHandlerAspect { private static Logger logger = LoggerFactory.getLogger(MethodRetryHandlerAspect.class); @Around("@annotation(com.sivalabs.springretrydemo.Retry)") public Object audit(ProceedingJoinPoint pjp) { Object result = null; result = retryableExecute(pjp); return result; } protected Object retryableExecute(final ProceedingJoinPoint pjp) { MethodSignature signature = (MethodSignature) pjp.getSignature(); Method method = signature.getMethod(); logger.debug("-----Retry Aspect---------"); logger.debug("Method: "+signature.toString()); Retry retry = method.getDeclaredAnnotation(Retry.class); int retryAttempts = retry.retryAttempts(); long sleepInterval = retry.sleepInterval(); Class<? extends Throwable>[] ignoreExceptions = retry.ignoreExceptions(); Task<Object> task = new Task<Object>() { @Override public Object execute() { try { return pjp.proceed(); } catch (Throwable e) { throw new RuntimeException(e); } } }; return TaskExecutionUtil.execute(task, retryAttempts, sleepInterval, ignoreExceptions); }} |
Вот и все. Нам просто нужно несколько тестов для его проверки.
Сначала создайте класс конфигурации AppConfig.java следующим образом:
|
01
02
03
04
05
06
07
08
09
10
|
import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration@ComponentScan@EnableAspectJAutoProxypublic class AppConfig {} |
И пару пустышек.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
import org.springframework.stereotype.Service;@Servicepublic class ServiceA { private int counter = 1; public void method1() { System.err.println("----method1----"); } @Retry(retryAttempts=5, ignoreExceptions={NullPointerException.class}) public void method2() { System.err.println("----method2 begin----"); if(counter != 3){ counter++; throw new NullPointerException(); } System.err.println("----method2 end----"); }} |
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
import java.io.IOException;import org.springframework.stereotype.Service;@Servicepublic class ServiceB { @Retry(retryAttempts = 2, ignoreExceptions={IOException.class}) public void method3() { System.err.println("----method3----"); if(1 == 1){ throw new ArrayIndexOutOfBoundsException(); } } @Retry public void method4() { System.err.println("----method4----"); }} |
Наконец, напишите простой тест Junit для вызова этих методов.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes=AppConfig.class)public class RetryTest { @Autowired ServiceA svcA; @Autowired ServiceB svcB; @Test public void testA() { svcA.method1(); } @Test public void testB() { svcA.method2(); } @Test(expected=RuntimeException.class) public void testC() { svcB.method3(); } @Test public void testD() { svcB.method4(); }} |
Да, я знаю, что мог бы написать эти методы тестирования немного лучше, но я надеюсь, что вы поняли идею.
Запустите тесты JUnit и просмотрите инструкцию log, чтобы проверить, происходит ли повторная попытка метода в случае исключения или нет.
- Случай № 1: при вызове ServiceA.method1 () метод MethodRetryHandlerAspect вообще не будет применяться.
- Случай № 2: при вызове ServiceA.method2 () мы поддерживаем счетчик и генерируем исключение NullPointerException 2 раза. Но мы пометили этот метод, чтобы игнорировать исключения NullPointerException. Таким образом, он будет продолжать попытки 5 раз. Но 3-й раз метод будет выполнен нормально и выйдет из метода нормально.
- Случай № 3: Когда вызывается ServiceB.method3 (), мы генерируем исключение ArrayIndexOutOfBoundsException, но этот метод помечается, чтобы игнорировать только только IOException. Таким образом, выполнение этого метода не будет повторено и немедленно вызывает исключение.
- Случай № 4: Когда вызывается ServiceB.method4 (), все в порядке, поэтому он должен завершиться с первой попытки как обычно.
Я надеюсь, что этот пример продемонстрирует достаточно хорошее использование Spring AOP в реальном мире 🙂
| Ссылка: | Повторная попытка выполнения метода с использованием Spring AOP от нашего партнера JCG Сивы Редди в блоге My Experiment on Technology . |