簡體   English   中英

在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()將運行

當然,如果您忽略在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
  • 我將切入點一分為二,因為我們可以在迭代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(..)處理該錯誤。

那么,為了避免注釋整個呼叫鏈,您需要怎么做? 只有一種方法(非常難看):手動記賬,即您的方面需要記住以前忽略的異常實例,因為相應的錯誤處理建議從未針對該異常運行。

因此,您需要像這樣更改您的方面(忽略手動嘗試捕獲等創建的多線程和嵌套異常等問題,以免使其變得更加復雜):

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

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM