问题
I'm trying add some monitoring when some specific exception occurs. For example, if I have an aspect like this:
@Aspect
public class LogAspect {
@AfterThrowing(value = "execution(* *(..))", throwing = "e")
public void log(JoinPoint joinPoint, Throwable e){
System.out.println("Some logging stuff");
}
}
And test class:
public class Example {
public void divideByZeroWithCatch(){
try{
int a = 5/0;
}
catch (ArithmeticException e){
System.out.println("Can not divide by zero");
}
}
public void divideByZeroWithNoCatch(){
int b = 5/0;
}
public static void main (String [] args){
Example e = new Example();
System.out.println("***** Calling method with catch block *****");
e.divideByZeroWithCatch();
System.out.println("***** Calling method without catch block *****");
e.divideByZeroWithNoCatch();
}
}
As an output i will get:
***** Calling method with catch block *****
Can not divide by zero
***** Calling method without catch block *****
Some logging stuff
I was wondering if there is way for me to intercept method execution just after throwing exception, do something in my advice and continue with executing code in corresponding catch block?
So that if i call divideByZeroWithCatch()
i can get:
Some logging stuff
Can not divide by zero
回答1:
Yes, you can. You need a handler()
pointcut:
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class LogAspect {
@AfterThrowing(value = "execution(* *(..))", throwing = "e")
public void log(JoinPoint thisJoinPoint, Throwable e) {
System.out.println(thisJoinPoint + " -> " + e);
}
@Before("handler(*) && args(e)")
public void logCaughtException(JoinPoint thisJoinPoint, Exception e) {
System.out.println(thisJoinPoint + " -> " + e);
}
}
Log output, assuming class Example
is in package de.scrum_master.app
:
***** Calling method with catch block *****
handler(catch(ArithmeticException)) -> java.lang.ArithmeticException: / by zero
Can not divide by zero
***** Calling method without catch block *****
execution(void de.scrum_master.app.Example.divideByZeroWithNoCatch()) -> java.lang.ArithmeticException: / by zero
execution(void de.scrum_master.app.Example.main(String[])) -> java.lang.ArithmeticException: / by zero
Exception in thread "main" java.lang.ArithmeticException: / by zero
at de.scrum_master.app.Example.divideByZeroWithNoCatch(Example.java:13)
at de.scrum_master.app.Example.main(Example.java:21)
Update: If you want to know where the exception handler is located, there is a simple way: use the enclosing joinpoint's static part. You can also get information about parameter names and types etc. Just use code completion in order to see which methods are available.
@Before("handler(*) && args(e)")
public void logCaughtException(
JoinPoint thisJoinPoint,
JoinPoint.EnclosingStaticPart thisEnclosingJoinPointStaticPart,
Exception e
) {
// Exception handler
System.out.println(thisJoinPoint.getSignature() + " -> " + e);
// Method signature + parameter types/names
MethodSignature methodSignature = (MethodSignature) thisEnclosingJoinPointStaticPart.getSignature();
System.out.println(" " + methodSignature);
Class<?>[] paramTypes = methodSignature.getParameterTypes();
String[] paramNames = methodSignature.getParameterNames();
for (int i = 0; i < paramNames.length; i++)
System.out.println(" " + paramTypes[i].getName() + " " + paramNames[i]);
// Method annotations - attention, reflection!
Method method = methodSignature.getMethod();
for (Annotation annotation: method.getAnnotations())
System.out.println(" " + annotation);
}
Now update your code like this:
package de.scrum_master.app;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
int id();
String name();
String remark();
}
package de.scrum_master.app;
public class Example {
@MyAnnotation(id = 11, name = "John", remark = "my best friend")
public void divideByZeroWithCatch(int dividend, String someText) {
try {
int a = 5 / 0;
} catch (ArithmeticException e) {
System.out.println("Can not divide by zero");
}
}
public void divideByZeroWithNoCatch() {
int b = 5 / 0;
}
public static void main(String[] args) {
Example e = new Example();
System.out.println("***** Calling method with catch block *****");
e.divideByZeroWithCatch(123, "Hello world!");
System.out.println("***** Calling method without catch block *****");
e.divideByZeroWithNoCatch();
}
}
Then the console log says:
***** Calling method with catch block *****
catch(ArithmeticException) -> java.lang.ArithmeticException: / by zero
void de.scrum_master.app.Example.divideByZeroWithCatch(int, String)
int dividend
java.lang.String someText
@de.scrum_master.app.MyAnnotation(id=11, name=John, remark=my best friend)
Can not divide by zero
***** Calling method without catch block *****
execution(void de.scrum_master.app.Example.divideByZeroWithNoCatch()) -> java.lang.ArithmeticException: / by zero
execution(void de.scrum_master.app.Example.main(String[])) -> java.lang.ArithmeticException: / by zero
Exception in thread "main" java.lang.ArithmeticException: / by zero
at de.scrum_master.app.Example.divideByZeroWithNoCatch(Example.java:14)
at de.scrum_master.app.Example.main(Example.java:22)
If that is good enough for you, then you are fine. But beware, the static part is not the full joinpoint, so you cannot access parameter values from there. In order to do that you have to do manual bookkeeping. And this is possibly expensive and can slow down your application. But for what it is worth, I show you how to do it:
package de.scrum_master.aspect;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
@Aspect
public class LogAspect {
private ThreadLocal<JoinPoint> enclosingJoinPoint;
@AfterThrowing(value = "execution(* *(..))", throwing = "e")
public void log(JoinPoint thisJoinPoint, Throwable e) {
System.out.println(thisJoinPoint + " -> " + e);
}
@Before("execution(* *(..)) && within(de.scrum_master.app..*)")
public void recordJoinPoint(JoinPoint thisJoinPoint) {
if (enclosingJoinPoint == null)
enclosingJoinPoint = ThreadLocal.withInitial(() -> thisJoinPoint);
else
enclosingJoinPoint.set(thisJoinPoint);
}
@Before("handler(*) && args(e)")
public void logCaughtException(JoinPoint thisJoinPoint, Exception e) {
// Exception handler
System.out.println(thisJoinPoint + " -> " + e);
// Method signature + parameter types/names
JoinPoint enclosingJP = enclosingJoinPoint.get();
MethodSignature methodSignature = (MethodSignature) enclosingJP.getSignature();
System.out.println(" " + methodSignature);
Class<?>[] paramTypes = methodSignature.getParameterTypes();
String[] paramNames = methodSignature.getParameterNames();
Object[] paramValues = enclosingJP.getArgs();
for (int i = 0; i < paramNames.length; i++)
System.out.println(" " + paramTypes[i].getName() + " " + paramNames[i] + " = " + paramValues[i]);
// Target object upon which method is executed
System.out.println(" " + enclosingJP.getTarget());
// Method annotations - attention, reflection!
Method method = methodSignature.getMethod();
for (Annotation annotation: method.getAnnotations())
System.out.println(" " + annotation);
}
}
Why do we need a ThreadLocal
member for the joinpoint bookkeeping? Well, because obviously we would get into problems in multi-threaded applications otherwise.
Now the console log says:
***** Calling method with catch block *****
handler(catch(ArithmeticException)) -> java.lang.ArithmeticException: / by zero
void de.scrum_master.app.Example.divideByZeroWithCatch(int, String)
int dividend = 123
java.lang.String someText = Hello world!
de.scrum_master.app.Example@4783da3f
@de.scrum_master.app.MyAnnotation(id=11, name=John, remark=my best friend)
Can not divide by zero
***** Calling method without catch block *****
execution(void de.scrum_master.app.Example.divideByZeroWithNoCatch()) -> java.lang.ArithmeticException: / by zero
execution(void de.scrum_master.app.Example.main(String[])) -> java.lang.ArithmeticException: / by zero
Exception in thread "main" java.lang.ArithmeticException: / by zero
at de.scrum_master.app.Example.divideByZeroWithNoCatch(Example.java:14)
at de.scrum_master.app.Example.main(Example.java:22)
回答2:
Depending on actual implementation of AspectJ features, it is possible. See kriegaex's answer for how to do it.
However, when using AspectJ features in certain scenarios, you might find that not all pointcut definitions are supported. One example of this is Spring Framework AOP, which only supports a subset of AspectJ features . What you can then do is have two methods: a non-exposed but instrumentable method that doesn't catch exceptions (the one you will instrument), and exposed method that does catches. Like this:
protected void divideByZeroNoCatch(int arg) {
int r = arg / 0;
}
public void divideByZeroSafe(int arg) {
try {
divideByZeroNoCatch(arg);
} catch(ArithmeticException ae) {
logException(ae);
}
}
After that, you can pointcut on divideByZeroNoCatch
, which will give you ability to do your AfterThrowing. Obviously, the pointcut will have to change a little. And this will not work if your implementation of AspectJ does not support instrumenting non-public methods.
来源:https://stackoverflow.com/questions/42092503/how-to-intercept-method-which-handles-its-own-exceptions-using-aspectj