免責聲明:我不 Spring的用戶,因此,我要在這裏提出一個純粹的AspectJ的解決方案。但它應該在Spring AOP中以相同的方式工作。您需要更改的唯一方法是從@DeclarePresedence
切換到@Order
以獲取寬高比配置,如Spring AOP manual中所述。
驅動程序:
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
new Application().doSomething();
}
public void doSomething() {
System.out.println("Doing something");
}
}
節流異常類:
package de.scrum_master.app;
public class ThrottlingException extends RuntimeException {
private static final long serialVersionUID = 1L;
public ThrottlingException(String arg0) {
super(arg0);
}
}
節流攔截:
爲了模擬節流的情況我創建了一個輔助方法isThrottled()
,其中3例隨機返回true
。
package de.scrum_master.aspect;
import java.util.Random;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import de.scrum_master.app.ThrottlingException;
@Aspect
public class ThrottlingInterceptor {
private static final Random RANDOM = new Random();
@Before("execution(* doSomething())")
public void invoke(JoinPoint thisJoinPoint) throws ThrottlingException {
System.out.println(getClass().getSimpleName() + " -> " + thisJoinPoint);
if (isThrottled()) {
throw new ThrottlingException("call throttled");
}
}
private boolean isThrottled() {
return RANDOM.nextInt(3) > 0;
}
}
重試攔截:
請注意,AspectJ的註解@DeclarePrecedence("RetryInterceptor, *")
說這個攔截器是之前任何其他的執行。請用兩個攔截器類上的@Order
註釋替換它。否則,@Around
建議無法捕獲限制攔截器引發的異常。
另外值得一提的是,這個攔截器不需要任何反射來實現重試邏輯,它直接在重試循環中使用連接點,以便重試thisJoinPoint.proceed()
。這可以很容易地被分解成實施不同類型的重試行爲的幫助方法或助手類。只要確保使用ProceedingJoinPoint
作爲參數而不是Callable
。
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclarePrecedence;
import de.scrum_master.app.ThrottlingException;
@Aspect
@DeclarePrecedence("RetryInterceptor, *")
public class RetryInterceptor {
private static int MAX_TRIES = 5;
private static int WAIT_MILLIS_BETWEEN_TRIES = 1000;
@Around("execution(* doSomething())")
public Object invoke(ProceedingJoinPoint thisJoinPoint) throws Throwable {
System.out.println(getClass().getSimpleName() + " -> " + thisJoinPoint);
ThrottlingException throttlingException = null;
for (int i = 1; i <= MAX_TRIES; i++) {
try {
return thisJoinPoint.proceed();
}
catch (ThrottlingException e) {
throttlingException = e;
System.out.println(" Throttled during try #" + i);
Thread.sleep(WAIT_MILLIS_BETWEEN_TRIES);
}
}
throw throttlingException;
}
}
控制檯登錄成功重試:
RetryInterceptor -> execution(void de.scrum_master.app.Application.doSomething())
ThrottlingInterceptor -> execution(void de.scrum_master.app.Application.doSomething())
Throttled during try #1
ThrottlingInterceptor -> execution(void de.scrum_master.app.Application.doSomething())
Throttled during try #2
ThrottlingInterceptor -> execution(void de.scrum_master.app.Application.doSomething())
Doing something
控制檯登錄失敗重試:
RetryInterceptor -> execution(void de.scrum_master.app.Application.doSomething())
ThrottlingInterceptor -> execution(void de.scrum_master.app.Application.doSomething())
Throttled during try #1
ThrottlingInterceptor -> execution(void de.scrum_master.app.Application.doSomething())
Throttled during try #2
ThrottlingInterceptor -> execution(void de.scrum_master.app.Application.doSomething())
Throttled during try #3
ThrottlingInterceptor -> execution(void de.scrum_master.app.Application.doSomething())
Throttled during try #4
ThrottlingInterceptor -> execution(void de.scrum_master.app.Application.doSomething())
Throttled during try #5
Exception in thread "main" de.scrum_master.app.ThrottlingException: call throttled
at de.scrum_master.aspect.ThrottlingInterceptor.invoke(ThrottlingInterceptor.aj:19)
at de.scrum_master.app.Application.doSomething_aroundBody0(Application.java:9)
at de.scrum_master.app.Application.doSomething_aroundBody1$advice(Application.java:22)
at de.scrum_master.app.Application.doSomething(Application.java:1)
at de.scrum_master.app.Application.main(Application.java:5)
隨意問有關我的回答任何後續問題。
更新:我不知道你是如何和RetryingCallable
類RetryPolicy
/接口工作,你沒有告訴我很多關於它。但是我做了什麼,得到它的工作是這樣的:
package de.scrum_master.app;
import java.util.concurrent.Callable;
public interface RetryPolicy<V> {
V apply(Callable<V> callable) throws Exception;
}
package de.scrum_master.app;
import java.util.concurrent.Callable;
public class DefaultRetryPolicy<V> implements RetryPolicy<V> {
private static int MAX_TRIES = 5;
private static int WAIT_MILLIS_BETWEEN_TRIES = 1000;
@Override
public V apply(Callable<V> callable) throws Exception {
Exception throttlingException = null;
for (int i = 1; i <= MAX_TRIES; i++) {
try {
return callable.call();
}
catch (ThrottlingException e) {
throttlingException = e;
System.out.println(" Throttled during try #" + i);
Thread.sleep(WAIT_MILLIS_BETWEEN_TRIES);
}
}
throw throttlingException;
}
}
package de.scrum_master.app;
import java.util.concurrent.Callable;
public class RetryingCallable<V> {
private RetryPolicy<V> retryPolicy;
private Callable<V> callable;
public RetryingCallable(Callable<V> callable, RetryPolicy<V> retryPolicy) {
this.callable = callable;
this.retryPolicy = retryPolicy;
}
public static <V> RetryingCallable<V> newRetryingCallable(Callable<V> callable, RetryPolicy<V> retryPolicy) {
return new RetryingCallable<V>(callable, retryPolicy);
}
public V call() throws Exception {
return retryPolicy.apply(callable);
}
}
現在改變重試攔截這樣的:
package de.scrum_master.aspect;
import java.util.concurrent.Callable;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclarePrecedence;
import de.scrum_master.app.DefaultRetryPolicy;
import de.scrum_master.app.RetryPolicy;
import de.scrum_master.app.RetryingCallable;
@Aspect
@DeclarePrecedence("RetryInterceptor, *")
public class RetryInterceptor {
private RetryPolicy<Object> retryPolicy = new DefaultRetryPolicy<>();
@Around("execution(* doSomething())")
public Object invoke(ProceedingJoinPoint thisJoinPoint) throws Throwable {
System.out.println(getClass().getSimpleName() + " -> " + thisJoinPoint);
return RetryingCallable.newRetryingCallable(
new Callable<Object>() {
@Override
public Object call() throws Exception {
return thisJoinPoint.proceed();
}
},
retryPolicy
).call();
}
}
日誌輸出將是非常類似於你之前看到過。對我來說這很好。
感謝您的回答。我已經在使用'@ Ordered'。在這裏有一個問題,因爲我使用'@ ThrowsException'的建議,我有種無限循環的感覺。但是你的代碼似乎可以用'@ Around'正常工作,我在這裏失蹤的細微差別是什麼? – AgentX
也許你的意思是'@ AfterThrowing'。那麼,正如名字所暗示的那樣,這個通知類型會在方法執行完成後攔截它們,'@ Around'包裝自己_方法執行,即可以在執行之前和之後執行某些操作,捕獲並處理異常,重試方法執行等。 '@ AfterThrowing'已經太遲了,拋出異常並且方法執行結束。此外,請查看我的日誌中的異常callstack:異常在'ThrottlingInterceptor.invoke'中引發,因此您可能錯誤地使用了'@ AfterThrowing'切入點。 – kriegaex
謝謝,我現在添加了一個循環並重新嘗試,正如你所解釋的那樣。最後還有一件事:在將來我想要使用一個通用的重試策略(它提供了良好的默認值,例如'3次重試','隨機性'和'指數退避'),正如我在上面的例子中所使用的('return RetryingCallable .newRetryingCallable(new Callable