在 AspectJ 中排除带注释的方法 [英] Exclude annotated methods in AspectJ

查看:30
本文介绍了在 AspectJ 中排除带注释的方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想排除这里的注释方法是代码.

Hi I want to exclude the annotated method here is the code.

@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 注释的方法,但是当这个方法被异常调用时,它会停止下一个方法的执行.例如

Above code does exclude the method which annotated with NoTryCatch but when this method get called with exception it halts the execution of next methods. e.g

@NoTryCatch
void test(){throws NullPointor..}

现在我按顺序调用方法

test()
test1()

test1() 没有运行.

test1() does not run.

如果我删除 !@annotation(android.mobile.peakgames.net.aspectjandroid.exception.NoTryCatch) test1() 运行

If I remove !@annotation(android.mobile.peakgames.net.aspectjandroid.exception.NoTryCatch) the test1() runs

推荐答案

当然如果忽略 test() 中抛出的异常,test1() 不会运行,即让它升级.由于未处理的异常,下一个方法永远不会被调用.我认为这正是您的方面的设计目的.为什么你期待不同的行为?如果您确实期待其他内容,请在评论中进行描述,我可以在我的答案的编辑中向您展示如何做到这一点.

Of course test1() does not run if you ignore the exception thrown in test(), i.e. let it escalate. Due to that unhandled exception the next method is never called. I thought this is exactly what your aspect is designed to do. Why are you expecting different behaviour? And if you do expect something else, then please describe it in a comment and I can show you in an edit of my answer how to do it.

在 OP 评论后更新:

好吧,你在这里遇到了一个自制的问题:如果方法 void caller() 调用 @NoTryCatch void callee(),当然是 callee() 不会被处理,就像设计的那样.相反,它升级到没有注释的 caller() ,因此方面将在那里处理它.调用者如何知道异常被调用者中的一个方面忽略了?或者这个方面怎么知道呢?将控制权返回给调用者时,被调用者的控制流已经结束.

Well, you are having a home-made problem here: If method void caller() calls @NoTryCatch void callee(), of course the exception in callee() will not be handled, just as designed. Instead it escalates up to caller() which is not annotated and thus the aspect will handle it there. How can the caller know that the exception was ignored by an aspect in the callee? Or how can the aspect know, for that matter? The callee's control flow has already ended when returning control to the caller.

这个异常处理的概念至少很棘手.我什至会称它为有问题的,因为调用链的最内部元素决定了所有外部元素都应该忽略异常.通常异常处理只是以另一种方式工作.调用者决定如何处理被调用者抛出的异常,而不是被调用者本身.所以我建议你改变你对异常处理的想法和观念.

This concept of exception handling is tricky at the very least. I would even call it questionable because the inner-most element of a call chain determines that all the outer elements should ignore an exception. Usually exception handling works just the other way. The caller determines how to handle an exception thrown by the callee, not the callee itself. So I advise you to change your idea and concept of exception handling.

话虽如此,我将通过一点 MCVE 向您展示我所说的确实发生在您的应用程序中.因为我不是 Android 开发人员并且希望它在任何 Java SE 机器上运行,所以我用模型模拟了 Android API 的相关部分:

Having said that, I will show you that what I said really happens in your application with a little MCVE. Because I am not an Android developer and want this to run on any Java SE machine, I emulated the relevant parts of the Android API like this with mock-ups:

Android API 模型:

package android.content;

public class Context {}

package android.app;

import android.content.Context;

public class Activity extends Context {}

只需登录控制台即可模拟警报对话框.

This one emulates an alert dialog by just logging to the console.

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 方面,略有优化:

基本上这正是您经过一些优化后的方面:

Basically this is exactly your aspect with a few optimisations:

  • 您将错误处理逻辑分为两个建议,一个是周围",一个是抛出后".这使得遵循实际控制流有点困难,因为在一个建议中记录错误,只是为了稍后在另一个建议中捕获并忽略相同的错误.因此,我决定将日志记录放入around"通知的catch"块中,以便更清楚地说明发生了什么.
  • 您的原始切入点仅针对 AspectActivity 类中的方法.因此,很明显,连接点的目标始终是 Activity,因此始终是 Context.将 target() 绑定到一个建议参数更清晰、类型更安全,并且可以让您摆脱丑陋的强制转换和 instanceof.
  • 我将您的切入点一分为二,因为我们可以稍后在迭代 2 中重复使用它们,见下文.
  • You split your error handling logic into two advices, one "around" and one "after throwing". This makes it a bit hard to follow the actual control flow because in one advice you log the error, only to later catch and ignore the same error in the other advice. Thus, I decided to pull the logging into the "catch" block of the "around" advice, making it clearer what happens.
  • Your original pointcut only targets methods in class AspectActivity. Thus, it is clear that the joinpoint's target is always an Activity and thus always a Context. Binding the target() to an advice parameter is clearer, more type-safe and gets you rid of ugly casts and instanceof.
  • I split your pointcut into two because we can re-use them both later in iteration 2, see below.
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(..) 中处理.
  • that the annotated method doSomethingElse() is executed and the error is not handled there,
  • but that the calling method run() triggers the advice instead, thus the error is handled there.
  • Even if you also annotate run(), the error would be handled in main(..).

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

So what do you need to do in order to avoid annotating the whole call chain? There is only one - quite ugly - way of doing this: manual bookkeeping, i.e. your aspect needs to remember exception instances it has ignored before because the corresponding error-handling advice has never run for that very exception.

因此,您需要像这样更改您的方面(忽略手动 try-catch 等创建的多线程和嵌套异常等问题,以免使其变得更加复杂):

Consequently you need to change your aspect like this (ignoring issues like multi-threading and nested exceptions created by manual try-catch etc. so as not to make it even more complicated):

方面,迭代 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)

如您所见,异常现在升级,正如您所说的那样崩溃"应用程序.

As you can see, the exception now escalates, "crashing" the application as you said you wanted it to.

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

P.S.: InheritableThreadLocal<Throwable> is your friend if you like the aspect to be thread-safe. Feel free to ask about it if you do need that but don't know what I am talking about.

PPS:如果您将 @NoTryCatch 注释从 doSomethingElse() 移到 doSomethingFancy,日志更改如下:

P.P.S.: If you move the @NoTryCatch annotation from doSomethingElse() down to doSomethingFancy, the log changes as follows:

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)

这篇关于在 AspectJ 中排除带注释的方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆