【问题标题】:Exclude annotated methods in AspectJ在 AspectJ 中排除带注释的方法
【发布时间】:2018-02-20 10:03:10
【问题描述】:

您好我想排除带注释的方法,这里是代码。

@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() 运行

【问题讨论】:

  • 这也适用于 Android。我对spring框架了解不多。
  • 基本上我在Android中使用aspectj插件。
  • 可以获取joinPoint的来源,其中包括应用到它的注释列表。我认为这是你最好的猜测。
  • 将此作为第一行添加到您的 aop 方法中: Method enclosing = new Object() {}.getClass().getEnclosureMethod();从该封闭变量中,您可以获得注释列表(如果我没记错的话)
  • 我们不能使用Pointcut 吗?

标签: java android aop aspectj


【解决方案1】:

当然,如果你忽略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 方面,略微优化:

基本上这正是你的一些优化方面:

  • 您将错误处理逻辑拆分为两个建议,一个是“周围”,一个是“抛出后”。这使得跟踪实际控制流有点困难,因为在一个建议中您记录了错误,只是为了稍后在另一个建议中捕获并忽略相同的错误。因此,我决定将日志记录拉入“around”建议的“catch”块中,以便更清楚地知道发生了什么。
  • 您原来的切入点只针对AspectActivity 类中的方法。因此,很明显,连接点的目标始终是Activity,因此始终是Context。将target() 绑定到advice 参数更清晰,类型更安全,并且让您摆脱丑陋的演员表和instanceof
  • 我将您的切入点一分为二,因为我们可以稍后在迭代 2 中重复使用它们,见下文。
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(..) 中处理。

那么你需要做些什么来避免注释整个调用链呢?只有一种 - 非常丑陋 - 这样做的方法:手动记账,即您的方面需要记住它之前忽略的异常实例,因为相应的错误处理建议从未针对该异常运行。

因此,您需要像这样更改您的方面(忽略手动 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)

如您所见,异常现在升级,如您所说的那样“崩溃”应用程序。

P.S.:如果你喜欢线程安全的方面,InheritableThreadLocal&lt;Throwable&gt; 是你的朋友。如果您确实需要,请随时询问,但不知道我在说什么。

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)

【讨论】:

  • 好的。假设我用NoTryCatch 注释了一个方法test(),现在它会抛出异常。应用程序必须崩溃。 exceptionMethod 不应处理异常。
  • 查看大量更新。但正如我所说:也许你不应该那样做,我不喜欢这种应用设计。
猜你喜欢
  • 1970-01-01
  • 2013-02-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多