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

查看:561
本文介绍了在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:

  • 您将错误处理逻辑分为两个建议,一个为建议",另一个为抛出后".这使得遵循实际的控制流程有些困难,因为在一个建议中您记录了错误,而只是后来又捕获并忽略了另一个建议中的相同错误.因此,我决定将日志记录放到周围"建议的捕获"块中,以便更清楚地了解发生的情况.
  • 您的原始切入点仅针对类AspectActivity中的方法.因此,很明显,连接点的目标始终是Activity,因此始终是Context.将target()绑定到advice参数更加清晰,类型安全,并且摆脱了丑陋的强制转换和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.

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

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<Throwable>是您的朋友,如果您希望该方面具有线程安全性.随意询问是否需要它,但不知道我在说什么.

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.

P.P.S .:如果将@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天全站免登陆