JDK动态代理解析,InvocationHandler的第一个参数的解析
前言
2023年04月04日
今天在复习Spring AOP的内容,在看到JDK动态代理时,积攒多年的疑问又发生了。
半年前在学习设计模式时的JDK动态代理时,没有学明白,似懂非懂,迷迷糊糊就混过去了,今天复习AOP,打算彻底把JDK动态代理弄懂。
JDK动态代理的流程
JDK动态代理是基本流程:
- 通过Proxy类的静态方法newProxyInstance()方法创建目标对象的代理对象
- 该方法需要三个参数
看代码,这是创建代理对象的类
import com.liumingkai.dao.UserDao; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @author 刘明凯 * @version 0.0.1 * @date 2023年4月4日 11:40 */ public class MyProxy implements InvocationHandler { // 声明目标类接口 private UserDao userDao; // 创建代理方法 public Object createProxy(UserDao userDao) { this.userDao = userDao; // 1. 类加载器,本类的类加载器 ClassLoader classLoader = MyProxy.class.getClassLoader(); // 2. 被代理对象实现所有接口 Class<?>[] interfaces = userDao.getClass().getInterfaces(); // 3. 使用代理类进行增强,返回的是代理对象 return Proxy.newProxyInstance(classLoader, interfaces, this); /** * 第一个参数classLoader 表示当前类的类加载器 * 第二个参数 interfaces 表示被代理对象身上的所有接口 * 第三个参数是 this 表示代理类JdkProxy本身,因为本类实现了InvocationHandler接口中的Invoke()方法 */ } /** * 所有动态代理类的方法调用,都会交由invoke()方法区处理 * * @param proxy 目标对象的代理对象 * @param method 将要被执行的方法信息(反射) * @param args 执行方法需要的参数 * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 前增强 System.out.println("模拟权限校验...") // 在目标类调用目标方法,传入参数 Object obj = method.invoke(userDao, args); // 后增强 System.out.println("请你确认...."); // 返回原始方法的返回值 return obj; } }
测试一下
// 创建代理对象 MyProxy myProxy = new MyProxy(); // 创建目标对象 UserDao userdao = new UserDaoImpl(); // 从代理对象中获取增强后的目标对象 UserDao userDaoPro = (UserDao) myProxy.createProxy(userdao); // 执行方法 userDaoPro.addUser();
测试结果,动态增强成功
问题
在研究时,发现invoke方法的第一个参数并没有使用,那为什么还要把这个参数写上呢???
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { .... }
- 第一个参数是Proxy,
- 第二个参数是调用方法的Method对象
- 第三个参数是原始方法的参数
对于后两个参数理解,但是对于第一个参数不懂。
黑马的教材对此参数的解析是 被代理的对象。
如果proxy参数是真实的目标对象, 那为什么不通过method.invoke(proxy,args)激活原始方法?而是通过将目标对象成为属性呢?
我尝试修改,发现陷入无限地递归中。
显然,黑马的教材中对此处的解释是错误的。
然后开始看Proxy的源码,看了半天看不懂。
搜索JDK动态代理的文章,也都没有对这个参数进行详细的解析。
后来终于找到了一个相关对这个参数解释的文章。
# Java中InvocationHandler接口中第一个参数proxy详解
我总结:
第一个参数是真正的代理对象,在invoke中可以通过该参数获取到代理对象本身,可以获取到代理对象身上的其他方法和信息。可以将此对象作为参数返回,实现代理对象的层层调用
看测试代码,我们验证获取到的代理对象与invoke()中的第一个参数proxy是否是同一个对象,我们打印一下两个对象的字节码对象。
// 创建代理对象 MyProxy myProxy = new MyProxy(); // 创建目标对象 UserDao userdao = new UserDaoImpl(); // 从代理对象中获取增强后的目标对象 UserDao userDaoPro = (UserDao) myProxy.createProxy(userdao); // 打印代理对象的字节码对象 System.out.println(userDaoPro.getClass()); // 执行方法 userDaoPro.addUser();
在invoke()中也打印一下字节码对象
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 前增强 System.out.println("模拟权限认证....."); // 在目标类调用目标方法,传入参数 Object obj = method.invoke(userDao, args); // 后增强 System.out.println("请你确认...."); return obj; }
看测试结果,是同一个字节码对象
应用
对于一些链式编程实现,例如构建者、装饰者等经典的设计模式,都是将本对象作为返回值。
代理也可以实现这种链式编程的设计。
来看设计
接口设计
public interface UserDao { UserDao fun(); }
实现类
public class UserDaoImpl implements UserDao { @Override public UserDao fun() { System.out.println("方法执行"); return this; } }
代理类
package com.liumingkai.aspect; import com.liumingkai.dao.UserDao; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @author 刘明凯 * @version 0.0.1 * @date 2023年4月4日 11:40 */ public class MyProxy implements InvocationHandler { // 声明目标类接口 private UserDao userDao; // 创建代理方法 public Object createProxy(UserDao userDao) { this.userDao = userDao; // 1. 类加载器 ClassLoader classLoader = MyProxy.class.getClassLoader(); // 2. 被代理对象实现所有接口 Class<?>[] interfaces = userDao.getClass().getInterfaces(); // 3. 使用代理类进行增强,返回的是代理对象 return Proxy.newProxyInstance(classLoader, interfaces, this); } /** * 所有动态代理类的方法调用,都会交由invoke()方法区处理 * * @param proxy 被代理的对象 * @param method 将要被执行的方法信息(反射) * @param args 执行方法需要的参数 * @return * @throws Throwable */ @Override public UserDao invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("增强了..."); method.invoke(userDao,args); return (UserDao) proxy; } }
测试
// 创建代理对象 MyProxy myProxy = new MyProxy(); // 创建目标对象 UserDao userdao = new UserDaoImpl(); // 从代理对象中获取增强后的目标对象 UserDao userDaoPro = (UserDao) myProxy.createProxy(userdao); UserDao userDaoPro2 = userDaoPro.fun(); // 判断两个对象是否是同一个对象 System.out.println(userDaoPro == userDaoPro2);
总结:
JDK动态代理的思路:
Proxy类的静态方法newProxyInstance()方法,通过类加载器、目标对象的所有接口、InvocationHandler的实现类,这三个参数能够创建代理对象。
当代理对象的方法执行时,会统一交给InvocationHandler的invoke()方法处理,同时将代理对象本身this作为第一个参数传入。
InvocationHandler中的invoke()方法有三个参数
public UserDao invoke(Object proxy, Method method, Object[] args)
- 第一个参数Object proxy ,是真正的代理对象,代理对象在执行方法时,会将this作为参数传递给invoke()方法。
通过该参数可以拿到代理对象身上的信息,也可以将此代理对象作为返回值返回,实现对代理对象的层层调用。 - 第二个参数Method,是原始方法的Method对象,可以获取原始方法的信息,通过反射来调用原始方法
- 第三个参数是原始方法的参数
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程