JDK动态代理解析,InvocationHandler的第一个参数的解析

前言

2023年04月04日
今天在复习Spring AOP的内容,在看到JDK动态代理时,积攒多年的疑问又发生了。
半年前在学习设计模式时的JDK动态代理时,没有学明白,似懂非懂,迷迷糊糊就混过去了,今天复习AOP,打算彻底把JDK动态代理弄懂。

JDK动态代理的流程

JDK动态代理是基本流程:

  1. 通过Proxy类的静态方法newProxyInstance()方法创建目标对象的代理对象
  2. 该方法需要三个参数

看代码,这是创建代理对象的类

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对象,可以获取原始方法的信息,通过反射来调用原始方法
  • 第三个参数是原始方法的参数
posted @   秋天Code  阅读(18)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程
点击右上角即可分享
微信分享提示