首页 > 其他分享 >动态代理模式下UndeclaredThrowableException的产生

动态代理模式下UndeclaredThrowableException的产生

时间:2022-11-12 11:01:10浏览次数:35  
标签:Exception run invoke 代理 public UndeclaredThrowableException 动态 throws

API文档

我们先来看下这个异常类的api文档:

Thrown by a method invocation on a proxy instance if its invocation handler's invoke method throws a checked exception (a Throwable that is not assignable to RuntimeException or Error) that is not assignable to any of the exception types declared in the throws clause of the method that was invoked on the proxy instance and dispatched to the invocation handler.

这段描述中介绍了异常会被抛出的情况:调用代理实例的增强方法,如果调用处理程序(增强器)的invoke方法中抛出一个检查异常,但该异常不能被throws子句中声明的任何异常捕获(默认是RuntimeException和Error),那么UndeclaredThrowableException这个异常就会被代理实例抛出。

代码演示

由于是使用JDK的动态代理进行演示,那肯定少不了接口类:

public interface Animal {
// 奔跑
void run();
}
复制代码

被代理类:

public class Pig implements Animal {
@Override
public void run() {
System.out.println("猪突猛进");
}
}
复制代码

以及增强器InvocationHandler

public class AnimalInvocationHandler implements InvocationHandler {

private final Object target;

public AnimalInvocationHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("增强方法 -> className: " + target.getClass().getSimpleName() + " methodName:" + method.getName());
method.invoke(target, args);
throw new Exception("throw not catch exception");
}
}
复制代码

一切准备就绪,开始测试

public static void main(String[] args) throws Exception {

// 1、创建 InvocationHandler 实例并设置代理的目标类对象
Animal pig = new Pig();
InvocationHandler invocationHandler = new AnimalInvocationHandler(pig);

// 2、创建代理类型,获取一个带有InvocationHandler参数的构造器
Class<?> proxyClass = Proxy.getProxyClass(Animal.class.getClassLoader(), Animal.class);
Constructor<?> ProxyConstructor = proxyClass.getConstructor(InvocationHandler.class);

// 3、以构造器的方式创建代理实例,执行增强方法
Animal animalProxy = (Animal) ProxyConstructor.newInstance(invocationHandler);
try {
animalProxy.run();
} catch (Exception e) {
e.printStackTrace();
}
}

启动main方法后,控制台打印:

增强方法 -> className:Pig  methodName:run
猪突猛进
java.lang.reflect.UndeclaredThrowableException
at com.sun.proxy.$Proxy0.run(Unknown Source)
at com.learn.springtest.undeclaredthrowable.MainClass.main(MainClass.java:40)
Caused by: java.lang.Exception: throw not catch exception
at com.learn.springtest.undeclaredthrowable.AnimalInvocationHandler.invoke(AnimalInvocationHandler.java:22)
... 2 more

从上面的结果输出中,可以明显看到:代理类抛出的这个异常,而根本原因是AnimalInvocationHandler的invoke方法中抛出了Exception异常。

解释:由于Exception不在throws子句中声明的任何异常(Animal#run方法没有声明抛出异常,默认就是RuntimeException和Error)的范围内,异常无法被捕获。最终,代理类上抛了UndeclaredThrowableException异常,事实也确实如此!

两个影响因素

从上面的叙述中,可以得到一个结论,那就是影响代理类能否抛出UndeclaredThrowableException的因素有两个:

  1. 被代理类的方法上声明的抛出异常;
  2. 增强器InvocationHandler的invoke方法中抛出的异常类型;

接下来,将以实验的方式验证这两个影响因素,毕竟伟大的领袖毛主席曾说过:实践是检验真理的唯一标准。

实验1:

其他代码不变,被代理类的方法上抛出Exception异常,改动如下:

void run() throws Exception;

执行main方法,控制台输出:

增强方法 -> className:Pig  methodName:run
猪突猛进
java.lang.Exception: throw not catch exception
at com.learn.springtest.undeclaredthrowable.AnimalInvocationHandler.invoke(AnimalInvocationHandler.java:22)
at com.sun.proxy.$Proxy0.run(Unknown Source)
at com.learn.springtest.undeclaredthrowable.MainClass.main(MainClass.java:40)

实验2:

其他代码不变,增强器AnimalInvocationHandler中抛出的异常改为RuntimeException,改动如下:

throw new RuntimeException("throw not catch exception");

执行main方法,控制台输出:

增强方法 -> className:Pig  methodName:run
猪突猛进
java.lang.RuntimeException: throw not catch exception
at com.learn.springtest.undeclaredthrowable.AnimalInvocationHandler.invoke(AnimalInvocationHandler.java:22)
at com.sun.proxy.$Proxy0.run(Unknown Source)
at com.learn.springtest.undeclaredthrowable.MainClass.main(MainClass.java:40)

通过上面实验中的针对性改动,两次的运行结果中都没有再出现UndeclaredThrowableException异常,那么这两个影响因素也就得到了证实。

字节码文件

生成代理实例的字节码文件

public static void main(String[] args) throws Exception {

// 测验代码,忽略
...

// 保存代理类
saveGeneratedJdkProxyFiles();
saveClass("Pig$Proxy0", proxyClass.getInterfaces(), "");
}

/**
* 开启设置:允许生成Java动态代理生成的类文件
*/
public static void saveGeneratedJdkProxyFiles() throws Exception {
Field field = System.class.getDeclaredField("props");
field.setAccessible(true);
Properties props = (Properties) field.get(null);
props.put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
}

/**
* 生成代理类 class 并保持到文件中
*
* @param className 生成的代理类名称
* @param interfaces 代理类的实现接口
* @param pathDir 代理类保存的目录路径,需要以目录分隔符"/"结尾
*/
public static void saveClass(String className, Class<?>[] interfaces, String pathDir) {
byte[] classFile = ProxyGenerator.generateProxyClass(className, interfaces);

// 如果目录不存在就新建所有子目录
Path path = Paths.get(pathDir);
if (!path.toFile().exists()) {
path.toFile().mkdirs();
}

String fullFilePath = pathDir + className + ".class";
try (FileOutputStream fos = new FileOutputStream(fullFilePath)) {
fos.write(classFile);
fos.flush();
System.out.println("代理类的class文件写入成功");
} catch (Exception e) {
e.printStackTrace();
}
}

执行main方法,在当前项目的根目录下可以找到一个名称为Pig$Proxy0.class的文件,它就是代理类的字节码文件。

贴一下代理类的字节码文件中run()增强方法:

public final void run() throws  {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

这样就可以很直观的看到,如果增强器InvocationHandler#invoke方法中抛出的异常,不能被RuntimeException或者Error捕获,最终就会抛出UndeclaredThrowableException异常。

其他代码不变,被代理类的方法上抛出IOException异常,执行main方法重新生成字节码文件

public final void run() throws IOException {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | IOException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

增强方法的throws子句中多了一个IOException类型的异常。

Tips

1、saveClass的第三个参数中传入的是空字符串,这样生成的代理类字节码文件就会出现在项目的根目录下,直接使用编辑器(如:IDEA)打开即可。

如果生成在其他地方,就需要使用java字节码文件的反编译工具,推荐使用 Java Decompiler

下载后解压,打开程序,直接把.class文件丢进去就行了。

2、开启生成字节文件的配置方法中,丢进去一个key值为“sun.misc.ProxyGenerator.saveGeneratedFiles”的配置项

如果无法生成字节码文件,那就将配置项的key值改为你当前JDK版本中对应的key值即可

详见源码:ProxyGenerator类的属性域saveGeneratedFiles

标签:Exception,run,invoke,代理,public,UndeclaredThrowableException,动态,throws
From: https://blog.51cto.com/u_15773567/5846567

相关文章

  • Java静态代理设计模式模式(多线程Runnable)
    静态代理设计模式,就是代理对象来帮你忙前忙后,你负责出席一下就好了。需要:1.实现相同的接口2.真实角色3.代理角色,里面有一个成员是接口对象(实际上是真实角色----接口实......
  • Java安全之动态加载字节码
    Java字节码简单说,Java字节码就是.class后缀的文件,里面存放Java虚拟机执行的指令。由于Java是一门跨平台的编译型语言,所以可以适用于不同平台,不同CPU的计算机,开发者只需......
  • ssh 代理-缓存ssh私钥
    当把私钥交给sshagent管理的好处当其他程序需要身份验证的时候可以将验证申请交给ssh-agent来完成整个认证过程。使用不同的密钥连接到不同的主机时,需要要手动指......
  • space 动态布局算法(vue3-ts、setup)
    动态布局组件演示效果<template><ulclass="space_ulflex-row":style="{'padding-top':`${hs}px`,'padding-left':`${ws}px`}"......
  • 用户动态权限菜单管理简单实现方式
    1.说明根据用户角色的权限进行菜单管理,根据拥有的权限访问范围内的菜单2.数据库表设计  2.1用户表CREATETABLE`sys_user`(`uid`int(11)NOTNULLAUTO_IN......
  • bladex动态配置字段
    步骤:1.添加业务字典 例如我在设备管理里加入两个动态字段url(监控链接)和 bindCar(绑定车辆id)vue在属性配置中的代码    此时业务字段命名与前端对应  ......
  • 软件设计模式白话文系列(六)代理模式
    1、描述代理模式属于结构型模式中的一种,通过对代理对象的调用来达到对原对象的增强、减弱作用。通过代理类的生成时机,我们将编译期就生成代理类的情况称之为静态代理模式,......
  • 动态sql(foreach)用法
    <selectid="getProjectEquipment"resultType="com.yeejoin.amos.boot.module.ugp.api.dto.ProjectResourceDto">SELECTequipment.name,equipment.`code`......
  • Android实战简易教程-第二十一枪(GridView动态添加Item)
    本例子实现在GridView的最后显示一个增加图片,点击图片动态增加内容item。1.main.xml:<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"xml......
  • HTTP代理购买如何选套餐
    爬虫工作离不开HTTP代理的支持,选择合适的HTTP代理套餐可以让工作事半功倍,但网上各种各样的套餐实在是太多了,太难选择了,爬虫业务千千万,对HTTP代理的需求都不一样,因此,针......