使用Spring AOP重试方法执行

本文展示了如何使用SpringAOP在实际项目中处理由于网络延迟或锁异常等问题导致的远程WebService调用或数据库查询失败。通过创建代理并实现重试逻辑,自动重试方法执行,提供了一个实用的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我的一位博客关注者发送了一封电子邮件,要求我显示“ Spring AOP的RealWorld用法”示例。 他提到,在大多数示例中,都演示了Spring AOP日志记录方法进入/退出事务管理安全性检查中的用法。

他想知道Spring AOP在“针对实际问题的真实项目”中的用法。 因此,我想展示如何在我的一个项目中使用Spring AOP来处理一个实际问题。

我们不会在开发阶段遇到任何问题,只有在负载测试期间或仅在生产环境中才知道。

例如:

  • 由于网络延迟问题而导致的远程WebService调用失败
  • 由于Lock异常等导致数据库查询失败

在大多数情况下,只需重试相同的操作就足以解决此类故障。

让我们看看如果发生任何异常,如何使用Spring AOP自动重试方法执行。 我们可以使用Spring AOP @Around建议为那些需要重试其方法的对象创建代理,并在Aspect中实现重试逻辑。

在继续实施这些Spring Advice和Aspect之前,首先让我们编写一个简单的实用程序来执行“任务” ,该任务将自动重试N次,而忽略给定的异常集。

public interface Task<T> {
 T execute();
}
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;
 }

}

我希望这种方法可以自我解释。 它要执行Task 并重noOfRetryAttempts次数,以防task.execute()方法抛出任何Exception且ignoreExceptions指示重试时要忽略的异常类型。

现在让我们创建一个Retry注释,如下所示:

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批注的方法的Aspect。

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
@Aspect
public 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配置类,如下所示:

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class AppConfig {

}

以及几个虚拟Service Bean。

import org.springframework.stereotype.Service;

@Service
public 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----");  
 }
}
import java.io.IOException;
import org.springframework.stereotype.Service;

@Service
public 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测试来调用这些方法。

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语句,以验证是否在发生Exception的情况下重试方法。

  • 情况1:调用ServiceA.method1()时,根本不会应用MethodRetryHandlerAspect。
  • 情况2:调用ServiceA.method2()时,我们正在维护一个计数器并抛出NullPointerException 2次。 但是我们已经标记了该方法以忽略NullPointerExceptions。 因此它将继续重试5次。 但是第三次​​方法将正常执行并正常退出该方法。
  • 情况3:调用ServiceB.method3()时,我们将抛出ArrayIndexOutOfBoundsException,但该方法被标记为仅忽略IOException。 因此,将不会重试此方法的执行,并且会立即引发Exception。
  • 情况4:调用ServiceB.method4()时,一切都很好,因此通常应在第一次尝试中退出。

我希望这个例子能说明Spring AOP在现实世界中有足够的用处:-)

翻译自: https://www.javacodegeeks.com/2016/02/retrying-method-execution-using-spring-aop.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值