java threadsafe 注解,在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;

}

}

Exclude any method which is annotated with 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..}

now I call methods in sequence

test()

test1()

test1() does not run.

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

解决方案

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.

Update after OP's comment:

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.

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 mock-ups:

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);

}

}

Marker annotation:

package android.mobile.peakgames.net.aspectjandroid.exception;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;

@Retention(RUNTIME)

public @interface NoTryCatch {}

Driver application:

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's aspect, slightly optimised:

Basically this is exactly your aspect with a few optimisations:

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;

}

}

}

Console log:

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()

The log clearly shows

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):

Aspect, iteration 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 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);

}

}

Console log, iteration 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 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.: 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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值