Exclude Annotated Methods In Aspectj
Solution 1:
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;
publicclassContext {}
package android.app;
import android.content.Context;
publicclassActivityextendsContext {}
This one emulates an alert dialog by just logging to the console.
package android.app;
import android.content.Context;
publicclassAlertDialog {
publicAlertDialog() {}
publicstaticclassBuilder {
privateString title;
privateString message;
publicBuilder(Context target) {}
publicBuildersetTitle(String title) {
this.title = title;
returnthis;
}
publicBuildersetMessage(String message) {
this.message = message;
returnthis;
}
publicvoidshow() {
System.out.println("ALERT DIALOG: " + title + " -> " + message);
}
}
}
package org.apache.http.auth;
publicclassAuthenticationExceptionextendsException {
privatestaticfinallongserialVersionUID=1L;
publicAuthenticationException(String message) {
super(message);
}
}
Marker annotation:
package android.mobile.peakgames.net.aspectjandroid.exception;
importstatic 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;
publicclassAspectActivityextendsActivity {
public String doSomething() {
System.out.println("Doing something");
return"something";
}
@NoTryCatchpublic String doSomethingElse() {
System.out.println("Doing something else");
thrownewRuntimeException("oops");
}
public String doSomethingFancy()throws AuthenticationException {
System.out.println("Doing something fancy");
thrownewAuthenticationException("uh-oh");
}
publicvoidrun()throws AuthenticationException {
doSomething();
doSomethingElse();
doSomethingFancy();
}
publicstaticvoidmain(String[] args)throws AuthenticationException {
newAspectActivity().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 anActivity
and thus always aContext
. Binding thetarget()
to an advice parameter is clearer, more type-safe and gets you rid of ugly casts andinstanceof
. - 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;
@AspectpublicclassExceptionHandlingAspect {
privatestaticfinalStringTAG= ExceptionHandlingAspect.class.getName();
@Pointcut("execution(* *(..)) && target(activity)")publicvoidmethodsOfInterest(AspectActivity activity) {}
@Pointcut("@annotation(android.mobile.peakgames.net.aspectjandroid.exception.NoTryCatch)")publicvoidannotationNoTryCatch() {}
@Around("methodsOfInterest(activity) && !annotationNoTryCatch()")public Object exceptionAroundMethod(ProceedingJoinPoint thisJoinPoint, AspectActivity activity) {
try {
return thisJoinPoint.proceed();
} catch (Throwable throwable) {
StringerrorMessage="Error " + throwable + " in method " + thisJoinPoint.getSignature();
Log.e(TAG, errorMessage);
Builderbuilder=newAlertDialog.Builder(activity);
if (throwable instanceof AuthenticationException)
builder.setTitle("Authentication Error").setMessage("You are not authenticated").show();
else
builder.setTitle("Error").setMessage(errorMessage).show();
returnnull;
}
}
}
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 inmain(..)
.
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;
@AspectpublicclassExceptionHandlingAspect {
privatestaticfinalStringTAG= ExceptionHandlingAspect.class.getName();
private Set<Throwable> ignoredErrors = newHashSet<>();
@Pointcut("execution(* *(..)) && target(activity)")publicvoidmethodsOfInterest(AspectActivity activity) {}
@Pointcut("@annotation(android.mobile.peakgames.net.aspectjandroid.exception.NoTryCatch)")publicvoidannotationNoTryCatch() {}
@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;
StringerrorMessage="Error " + throwable + " in method " + thisJoinPoint.getSignature();
Log.e(TAG, errorMessage);
Builderbuilder=newAlertDialog.Builder(activity);
if (throwable instanceof AuthenticationException)
builder.setTitle("Authentication Error").setMessage("You are not authenticated").show();
else
builder.setTitle("Error").setMessage(errorMessage).show();
returnnull;
}
}
@AfterThrowing(value = "methodsOfInterest(activity) && annotationNoTryCatch()", throwing = "throwable")publicvoidignoreExceptions(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<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.: 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)
Post a Comment for "Exclude Annotated Methods In Aspectj"