Один из последователей моего блога отправляет электронное письмо с просьбой показать пример использования 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 . |