排除 AspectJ 中的注释方法
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() 运行s
当然 test1()
不会 运行 如果您忽略 test()
中抛出的异常,即让它升级。由于未处理的异常,永远不会调用 next 方法。我认为这正是您设计的方面。为什么你期待不同的行为?如果您确实有其他期望,那么请在评论中描述它,我可以在我的答案编辑中向您展示如何去做。
OP评论后更新:
嗯,你这里有一个home-made问题:如果方法void caller()
调用@NoTryCatch void callee()
,当然callee()
中的异常不会被处理,就像设计的。相反,它会升级到未注释的 caller()
,因此方面将在那里处理它。调用者如何知道被调用者中的某个方面忽略了异常?或者就此而言,方面如何知道?将控制权返回给调用者时,被调用者的控制流已经结束。
这个异常处理的概念至少是棘手的。我什至认为它有问题,因为调用链的 inner-most 元素决定了所有外部元素都应该忽略异常。通常异常处理只是以另一种方式工作。调用者决定如何处理被调用者抛出的异常,而不是被调用者本身。所以我建议你改变一下你对异常处理的想法和观念。
说了这么多,我会用一点点 MCVE 向你展示我所说的在你的应用程序中确实发生了。因为我不是 Android 开发人员并且希望在任何 Java SE 机器上将其 运行,所以我模拟了 Android API 的相关部分,如下所示mock-ups:
Android API mock-ups:
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的方面,略微优化:
基本上这正是您的方面,并进行了一些优化:
- 您将错误处理逻辑拆分为两个建议,一个 "around" 和一个 "after throwing"。这使得遵循实际的控制流有点困难,因为在一个建议中你记录了错误,只是为了稍后捕获并忽略另一个建议中的相同错误。因此,我决定将日志记录拉到 "around" 建议的 "catch" 块中,使发生的事情更清楚。
- 您的原始切入点仅针对 class
AspectActivity
中的方法。因此,很明显,连接点的目标始终是 Activity
,因此始终是 Context
。将 target()
绑定到建议参数更清晰,更 type-safe 并且可以摆脱丑陋的转换和 instanceof
.
- 我将你的切入点分成两部分,因为我们可以在稍后的迭代 2 中re-use 它们,见下文。
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(..)
中处理。
那么您需要做什么才能避免注释整个调用链?只有一种 - 非常丑陋 - 这样做的方法:手动簿记,即你的方面需要记住它之前忽略的异常实例,因为相应的 error-handling 建议从来没有 运行 那个异常。
因此你需要像这样改变你的方面(忽略像 multi-threading 和手动创建的嵌套异常 try-catch 等问题,以免使其变得更加复杂):
方面,迭代 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)
如您所见,异常现在升级,"crashing" 应用程序正如您所说的那样。
P.S.: InheritableThreadLocal<Throwable>
是你的朋友,如果你喜欢 thread-safe 的相位。如果您确实需要但不知道我在说什么,请随时询问。
P.P.S.: 如果把@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)
你好我想排除注释方法这里是代码。
@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() 运行s
当然 test1()
不会 运行 如果您忽略 test()
中抛出的异常,即让它升级。由于未处理的异常,永远不会调用 next 方法。我认为这正是您设计的方面。为什么你期待不同的行为?如果您确实有其他期望,那么请在评论中描述它,我可以在我的答案编辑中向您展示如何去做。
OP评论后更新:
嗯,你这里有一个home-made问题:如果方法void caller()
调用@NoTryCatch void callee()
,当然callee()
中的异常不会被处理,就像设计的。相反,它会升级到未注释的 caller()
,因此方面将在那里处理它。调用者如何知道被调用者中的某个方面忽略了异常?或者就此而言,方面如何知道?将控制权返回给调用者时,被调用者的控制流已经结束。
这个异常处理的概念至少是棘手的。我什至认为它有问题,因为调用链的 inner-most 元素决定了所有外部元素都应该忽略异常。通常异常处理只是以另一种方式工作。调用者决定如何处理被调用者抛出的异常,而不是被调用者本身。所以我建议你改变一下你对异常处理的想法和观念。
说了这么多,我会用一点点 MCVE 向你展示我所说的在你的应用程序中确实发生了。因为我不是 Android 开发人员并且希望在任何 Java SE 机器上将其 运行,所以我模拟了 Android API 的相关部分,如下所示mock-ups:
Android API mock-ups:
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的方面,略微优化:
基本上这正是您的方面,并进行了一些优化:
- 您将错误处理逻辑拆分为两个建议,一个 "around" 和一个 "after throwing"。这使得遵循实际的控制流有点困难,因为在一个建议中你记录了错误,只是为了稍后捕获并忽略另一个建议中的相同错误。因此,我决定将日志记录拉到 "around" 建议的 "catch" 块中,使发生的事情更清楚。
- 您的原始切入点仅针对 class
AspectActivity
中的方法。因此,很明显,连接点的目标始终是Activity
,因此始终是Context
。将target()
绑定到建议参数更清晰,更 type-safe 并且可以摆脱丑陋的转换和instanceof
. - 我将你的切入点分成两部分,因为我们可以在稍后的迭代 2 中re-use 它们,见下文。
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(..)
中处理。
那么您需要做什么才能避免注释整个调用链?只有一种 - 非常丑陋 - 这样做的方法:手动簿记,即你的方面需要记住它之前忽略的异常实例,因为相应的 error-handling 建议从来没有 运行 那个异常。
因此你需要像这样改变你的方面(忽略像 multi-threading 和手动创建的嵌套异常 try-catch 等问题,以免使其变得更加复杂):
方面,迭代 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)
如您所见,异常现在升级,"crashing" 应用程序正如您所说的那样。
P.S.: InheritableThreadLocal<Throwable>
是你的朋友,如果你喜欢 thread-safe 的相位。如果您确实需要但不知道我在说什么,请随时询问。
P.P.S.: 如果把@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)