我的一位博客关注者发送了一封电子邮件,要求我显示“ 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