[英]Exclude annotated methods in AspectJ
嗨,我想排除注釋的方法,這里是代碼。
@Aspect
public class ExceptionHandlingAspect {
private static final String TAG = ExceptionHandlingAspect.class.getName();
@Pointcut("execution(* android.mobile.peakgames.net.aspectjandroid.AspectActivity.*(..)) " +
"&& !@annotation(android.mobile.peakgames.net.aspectjandroid.exception.NoTryCatch)")
public void exceptionEntryPoint() {
}
@AfterThrowing(pointcut = "exceptionEntryPoint()", throwing = "throwable")
public void exceptionMethod(JoinPoint joinPoint, Throwable throwable) {
Log.e(TAG, "Exception caught : " + throwable + " on method : " + joinPoint.getSignature());
if (joinPoint.getTarget() instanceof Activity) {
if (throwable instanceof AuthenticationException) {
new AlertDialog.Builder((Context) joinPoint.getTarget())
.setTitle("Authentication Error")
.setMessage("You are not authenticated")
.show();
} else {
new AlertDialog.Builder((Context) joinPoint.getTarget())
.setTitle("Error")
.setMessage("Error occurred at : " + joinPoint.getSignature() + " " +
"Exception : " + throwable)
.show();
}
}
}
@Around(value = "exceptionEntryPoint()")
public Object exceptionAroundMethod(ProceedingJoinPoint joinPoint) {
try {
return joinPoint.proceed();
} catch (Throwable ignored) {
}
return null;
}
}
排除任何用NoTryCatch
注釋的方法
上面的代碼確實排除了用NoTryCatch注釋的方法,但是當該方法被異常調用時,它將終止下一個方法的執行。 例如
@NoTryCatch
void test(){throws NullPointor..}
現在我依次調用方法
test()
test1()
test1()無法運行。
如果我刪除!@annotation(android.mobile.peakgames.net.aspectjandroid.exception.NoTryCatch)
則test1()將運行
當然,如果您忽略在test()
引發的異常test()
即讓其升級test()
,則test1()
不會運行。 由於存在未處理的異常,因此永遠不會調用下一個方法。 我認為這正是您要設計的方面。 您為什么期望不同的行為? 如果您確實還有其他期望,請在評論中進行說明,然后在編輯答案的過程中向您展示。
OP發表評論后更新:
好的,您在這里遇到了一個自制的問題:如果方法void caller()
調用@NoTryCatch void callee()
,則不會像設計的那樣處理callee()
的異常。 相反,它會升級到未注釋的caller()
,因此方面將在那里處理它。 調用方如何知道被調用方的某個方面忽略了該異常? 或對此如何知道? 當將控制權返回給調用方時,被調用方的控制流已經結束。
異常處理的概念至少非常棘手。 我什至認為這是有問題的,因為調用鏈的最內層元素確定所有外層元素都應忽略異常。 通常,異常處理是另一種方式。 調用方確定如何處理被調用方引發的異常,而不是被調用方本身。 因此,我建議您更改您的異常處理概念。
話雖如此,我將向您展示我所說的確實在您的應用程序中發生了一點MCVE 。 因為我不是Android開發人員,並且希望它可以在任何Java SE機器上運行,所以我使用模型模擬了Android API的相關部分,例如:
Android API實體模型:
package android.content;
public class Context {}
package android.app;
import android.content.Context;
public class Activity extends Context {}
這只是通過登錄到控制台來模擬警報對話框。
package android.app;
import android.content.Context;
public class AlertDialog {
public AlertDialog() {}
public static class Builder {
private String title;
private String message;
public Builder(Context target) {}
public Builder setTitle(String title) {
this.title = title;
return this;
}
public Builder setMessage(String message) {
this.message = message;
return this;
}
public void show() {
System.out.println("ALERT DIALOG: " + title + " -> " + message);
}
}
}
package org.apache.http.auth;
public class AuthenticationException extends Exception {
private static final long serialVersionUID = 1L;
public AuthenticationException(String message) {
super(message);
}
}
標記注釋:
package android.mobile.peakgames.net.aspectjandroid.exception;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
@Retention(RUNTIME)
public @interface NoTryCatch {}
驅動程序應用程序:
package android.mobile.peakgames.net.aspectjandroid;
import org.apache.http.auth.AuthenticationException;
import android.app.Activity;
import android.mobile.peakgames.net.aspectjandroid.exception.NoTryCatch;
public class AspectActivity extends Activity {
public String doSomething() {
System.out.println("Doing something");
return "something";
}
@NoTryCatch
public String doSomethingElse() {
System.out.println("Doing something else");
throw new RuntimeException("oops");
}
public String doSomethingFancy() throws AuthenticationException {
System.out.println("Doing something fancy");
throw new AuthenticationException("uh-oh");
}
public void run() throws AuthenticationException {
doSomething();
doSomethingElse();
doSomethingFancy();
}
public static void main(String[] args) throws AuthenticationException {
new AspectActivity().run();
}
}
OP的方面略有優化:
基本上,這是您的方面經過一些優化:
AspectActivity
類中的方法。 因此,很明顯,連接點的目標始終是Activity
,因此始終是Context
。 將target()
綁定到advice參數更加清晰,類型安全,並且擺脫了丑陋的強制轉換和instanceof
。 package de.scrum_master.aspect;
import org.apache.http.auth.AuthenticationException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.mobile.peakgames.net.aspectjandroid.AspectActivity;
import android.util.Log;
@Aspect
public class ExceptionHandlingAspect {
private static final String TAG = ExceptionHandlingAspect.class.getName();
@Pointcut("execution(* *(..)) && target(activity)")
public void methodsOfInterest(AspectActivity activity) {}
@Pointcut("@annotation(android.mobile.peakgames.net.aspectjandroid.exception.NoTryCatch)")
public void annotationNoTryCatch() {}
@Around("methodsOfInterest(activity) && !annotationNoTryCatch()")
public Object exceptionAroundMethod(ProceedingJoinPoint thisJoinPoint, AspectActivity activity) {
try {
return thisJoinPoint.proceed();
} catch (Throwable throwable) {
String errorMessage = "Error " + throwable + " in method " + thisJoinPoint.getSignature();
Log.e(TAG, errorMessage);
Builder builder = new AlertDialog.Builder(activity);
if (throwable instanceof AuthenticationException)
builder.setTitle("Authentication Error").setMessage("You are not authenticated").show();
else
builder.setTitle("Error").setMessage(errorMessage).show();
return null;
}
}
}
控制台日志:
Doing something
Doing something else
[de.scrum_master.aspect.ExceptionHandlingAspect] Error java.lang.RuntimeException: oops in method void android.mobile.peakgames.net.aspectjandroid.AspectActivity.run()
ALERT DIALOG: Error -> Error java.lang.RuntimeException: oops in method void android.mobile.peakgames.net.aspectjandroid.AspectActivity.run()
日志清楚地顯示
doSomethingElse()
已執行,並且錯誤未在那里處理, run()
會改為觸發建議,因此在那里處理了錯誤。 run()
,也會在main(..)
處理該錯誤。 那么,為了避免注釋整個呼叫鏈,您需要怎么做? 只有一種方法(非常難看):手動記賬,即您的方面需要記住以前忽略的異常實例,因為相應的錯誤處理建議從未針對該異常運行。
因此,您需要像這樣更改您的方面(忽略手動嘗試捕獲等創建的多線程和嵌套異常等問題,以免使其變得更加復雜):
方面,迭代2:
package de.scrum_master.aspect;
import java.util.HashSet;
import java.util.Set;
import org.apache.http.auth.AuthenticationException;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.mobile.peakgames.net.aspectjandroid.AspectActivity;
import android.util.Log;
@Aspect
public class ExceptionHandlingAspect {
private static final String TAG = ExceptionHandlingAspect.class.getName();
private Set<Throwable> ignoredErrors = new HashSet<>();
@Pointcut("execution(* *(..)) && target(activity)")
public void methodsOfInterest(AspectActivity activity) {}
@Pointcut("@annotation(android.mobile.peakgames.net.aspectjandroid.exception.NoTryCatch)")
public void annotationNoTryCatch() {}
@Around("methodsOfInterest(activity) && !annotationNoTryCatch()")
public Object exceptionAroundMethod(ProceedingJoinPoint thisJoinPoint, AspectActivity activity) throws Throwable {
try {
return thisJoinPoint.proceed();
} catch (Throwable throwable) {
if (ignoredErrors.contains(throwable))
throw throwable;
String errorMessage = "Error " + throwable + " in method " + thisJoinPoint.getSignature();
Log.e(TAG, errorMessage);
Builder builder = new AlertDialog.Builder(activity);
if (throwable instanceof AuthenticationException)
builder.setTitle("Authentication Error").setMessage("You are not authenticated").show();
else
builder.setTitle("Error").setMessage(errorMessage).show();
return null;
}
}
@AfterThrowing(value = "methodsOfInterest(activity) && annotationNoTryCatch()", throwing = "throwable")
public void ignoreExceptions(JoinPoint thisJoinPoint, AspectActivity activity, Throwable throwable) {
ignoredErrors.add(throwable);
}
}
控制台日志,迭代2:
Doing something
Doing something else
Exception in thread "main" java.lang.RuntimeException: oops
at android.mobile.peakgames.net.aspectjandroid.AspectActivity.doSomethingElse(AspectActivity.java:17)
at android.mobile.peakgames.net.aspectjandroid.AspectActivity.run_aroundBody4(AspectActivity.java:27)
at android.mobile.peakgames.net.aspectjandroid.AspectActivity.run_aroundBody5$advice(AspectActivity.java:34)
at android.mobile.peakgames.net.aspectjandroid.AspectActivity.run(AspectActivity.java:1)
at android.mobile.peakgames.net.aspectjandroid.AspectActivity.main(AspectActivity.java:32)
如您所見,異常現在升級,使您想要的應用程序“崩潰”。
PS:如果您希望方面是線程安全的,則InheritableThreadLocal<Throwable>
是您的朋友。 隨意詢問是否需要它,但不知道我在說什么。
PPS:如果將@NoTryCatch
注釋從doSomethingElse()
向下移動到doSomethingFancy
,則日志更改如下:
Doing something
Doing something else
[de.scrum_master.aspect.ExceptionHandlingAspect] Error java.lang.RuntimeException: oops in method String android.mobile.peakgames.net.aspectjandroid.AspectActivity.doSomethingElse()
ALERT DIALOG: Error -> Error java.lang.RuntimeException: oops in method String android.mobile.peakgames.net.aspectjandroid.AspectActivity.doSomethingElse()
Doing something fancy
Exception in thread "main" org.apache.http.auth.AuthenticationException: uh-oh
at android.mobile.peakgames.net.aspectjandroid.AspectActivity.doSomethingFancy(AspectActivity.java:22)
at android.mobile.peakgames.net.aspectjandroid.AspectActivity.run_aroundBody4(AspectActivity.java:28)
at android.mobile.peakgames.net.aspectjandroid.AspectActivity.run_aroundBody5$advice(AspectActivity.java:34)
at android.mobile.peakgames.net.aspectjandroid.AspectActivity.run(AspectActivity.java:1)
at android.mobile.peakgames.net.aspectjandroid.AspectActivity.main(AspectActivity.java:32)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.