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.Exception
和java.lang.Error
,以及作为java.lang.Exception
和java.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.java
的create
方法中没有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》
喜欢就点个吧