ByteBuddy(六)—使用@OnMethodExit Advice拦截异常

Advice代码可以拦截从函数代码抛出的异常。

Advice代码还可以将异常抑制为空,或者抛出一个新的异常来替换抛出的异常。

本章介绍了可以拦截异常的Advice代码。

这是本章中使用的功能代码:

public class DataProducer{
    public void create(String data){
        if(data == null)
            throw new IllegalArgumentException("Data cannot be null");
        System.out.println("create data:" + data);
    }
}

函数代码有一个create方法,它接受一个java.lang.String参数。
当数据参数为空时,该方法会抛出IllegalArgumentException,否则该方法会在屏幕上打印带有数据参数值的状态。

这是本章中使用的Advice代码:

public class ExceptionInterceptor{
    public static Logger logger = Logger.getLogger(ExceptionInterceptor.class.getName());

    @Advice.OnMethodExit(onThrowable=Throwable.class)
    public static void methodEnd(@Advice.Thrown(readOnly=false) Throwable excp){
        if(excp == null){
            logger.info("Method end with no exception");
        }else{
            logger.info("Method end with exception:\n" + "==========================\n" + "Exception type:" + excp.getClass().getName() + "\n" + "Exception message:" + excp.getMessage());
        }
        // 这里将异常置空
        excp = null;
    }
}

这个Main1.java用于测试检测结果

public class Main1{
    public static void main(String args[]){
        new DataProducer().create(null);
    }
}

Main1.java是一个可执行的java程序。
它实例化DataProducer.java的一个实例,然后使用空参数调用create方法。
在没有maven构建过程的情况下执行Main1.java,程序会抛出IllegalArgumentException,因为数据参数为空:

Exception in thread "main"
java.lang.TllegalArgumentException: data cannot be null 
at com.wpixel.bytebuddy.chapter1.DataProducer.create(DataProducer.java: 15) 
at com.wpixel.bytebuddy.chapter1.Main1.main(Main1. java:14)

之后,启动maven构建过程,然后执行Main1.java,程序将在consle上打印以下结果:

Aug 01, 2020 1:00:00 PM com.wpixel.bytebuddy.chapter1.DataProducer create INFO: Method end with exception:
Exception type: java.lang.IllegalArgumentException Exception message: Data cannot be null

在执行之后,main方法代码将在屏幕上打印异常信息。
但是,屏幕上没有显示异常堆栈跟踪,这是因为Advice代码将异常抑制为空。

Advice代码只能截获使用@OnMethodExit注解的方法中的异常@OnMethodExit注解必须在onThrowable属性中指定Java异常类名。

@Advice.OnMethodExit(onThrowable=Throwable.class)
public static void methodEnd(
            @Advice.Thrown(readOnly=false) Throwable excp){
    //
}

默认情况下,如果create方法出现异常,将跳过Advice方法。
要使插入指令的代码继续执行Advice代码,必须使用onThrowable属性。

如果从检测代码(main方法)引发的异常与onTrowable属性中定义的异常的类或派生类等效,则检测方法将继续执行Advice方法。

注意到methodEnd的参数名为excp,用@Advice.Thrown注解进行了注解。
此参数捕获从检测方法引发的异常。
例如,如果检测的方法抛出IllegalArgumentException,那么excp参数的值将是从检测的方法中抛出的IllegalArgumentException的实例。

要更改从插入指令的方法引发的异常,@Advice.Thrown注解的readOnly属性必须设置为false(可更改)。

在本例中,Advice方法捕获异常并在屏幕上打印异常类及其异常消息。
插入指令的代码不会引发异常,因为Advice代码禁止了异常:

excp = null;

如果Advice代码中省略了excp = null;,那么插入指令的方法将抛出异常:

Aug 01, 2020 1:00:00 PM com.wpixel.bytebuddy.chapter1.DataProducer create
INFO: Method end with exception:
==========================
Exception type: java.lang.IllegalArgumentException
Exception message: Data cannot be null
Exception in thread "main" java.lang.IllegalArgumentException: data cannot be null
at com.wpixel.bytebuddy.chapter1.DataProducer.create(DataProducer.java:5)
at com.wpixel.bytebuddy.chapter1.Main1.main(Main.java:14)

如果Main1.java对这行代码的更改:

new DataProducer().createData("create");

然后,插入指令的代码将在屏幕上生成此结果

create data: test
Oct 1, 2020 1:00:00 PM com.wpixel.bytebuddy.chapter1.DataProducer create
INFO: Method end with no exception

@Thrown注释参数的数据类型

excp参数的数据类型必须是同一类或从检测代码中抛出的异常类的派生类。
否则,插入指令的代码将抛出运行时异常java.lang.ClassCastException
例如:

public static void methodEnd(
          @Advice.Thrown(readOnly=false) Error excp){
    //
}

注意到excp参数的数据类型为java.lang.Error,这将在应用程序运行时导致ClassCastException,因为java无法将java.lang.IllegalArgumentException转换为java.lang Error

onThrowable属性的数据类型

Advice代码只能有一个OnMethodExit Advice。
因此,要管理advice方法中的所有异常类型,onThrowable属性应该使用Throwable.class,该类使Advice方法能够捕获java.lang.Exceptionjava.lang.Error,以及作为java.lang.Exceptionjava.lang.Error的派生类。

如果从插入指令的代码引发的异常与onThrowable属性中指定的类不匹配,则将跳过Advice方法。
例如,使用此配置

@Advice.OnMethodExit(onThrowable=Exception.class)

如果插入指令的代码抛出java.lang.Error,则将跳过Advice方法。

从Advice方法引发新异常

Advice方法可以抛出新的异常,这会改变从插入指令的代码抛出的原始异常。
如果ExceptionInterceptor.java变更代码

throw new RuntimeException("Runtime exception");

插入指令的代码将抛出Runtime Exception

Advice方法抛出checked exception而不是Runtime Exception,并且methodEnd添加了有效的throws语句?

@Advice.OnMethodExit(onThrowable=Throwable.class)
public static void methodEnd(
        @Advice.Thrown(readOnly=false) Throwable excp) 
        throws Exception{
    throw new Exception("checked exception");
}

插入指令的代码也将抛出"checked exception",类似于在Advice方法中抛出Runtime Exception的结果。

但是,请注意,函数方法在DataProducer.javacreate方法中没有throws语句

public class DataProducer{
    public void create(String data){
        if(data == null)
            throw new IllegalArgumentException("Data cannot be null");
        System.out.println("create data:" + data);
    }
}

根据Java编译过程,当create方法抛出异常时,create方法必须声明throws语句,否则,程序将收到编译错误。
然而,在Java字节码格式中,当程序抛出throw异常时,throws语句是可选的。
因此,生成的代码执行时不会出现运行时错误。

这是Java编程和Java字节码编程之间的重要区别。
Java编程处理Java文件,Java字节码编程处理Java类文件。
它们有不同的语法,异常管理就是其中一个例子。

结论

本章解释:

  • 如何使用@OnMethodExit注解的onThrowable属性管理异常
  • 如何通过@thrown注解参数将插入指令的代码引发的异常映射到Advice方法
  • 如何抑制异常

bytebuddy书籍《Java Interceptor Development with ByteBuddy: Fundamental》

喜欢就点个吧

你可能感兴趣的:(ByteBuddy(六)—使用@OnMethodExit Advice拦截异常)