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

前言

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

JDK动态代理的流程

JDK动态代理是基本流程:

  1. 通过Proxy类的静态方法newProxyInstance()方法创建目标对象的代理对象
  2. 该方法需要三个参数
    JDK动态代理解析,InvocationHandler的第一个参数的解析_第1张图片
    JDK动态代理解析,InvocationHandler的第一个参数的解析_第2张图片

JDK动态代理解析,InvocationHandler的第一个参数的解析_第3张图片

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

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();

测试结果,动态增强成功
JDK动态代理解析,InvocationHandler的第一个参数的解析_第4张图片

问题

在研究时,发现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;  
}

看测试结果,是同一个字节码对象
JDK动态代理解析,InvocationHandler的第一个参数的解析_第5张图片

应用

对于一些链式编程实现,例如构建者、装饰者等经典的设计模式,都是将本对象作为返回值。
代理也可以实现这种链式编程的设计。
来看设计
接口设计

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动态代理解析,InvocationHandler的第一个参数的解析_第6张图片

总结:

JDK动态代理的思路:
Proxy类的静态方法newProxyInstance()方法,通过类加载器、目标对象的所有接口、InvocationHandler的实现类,这三个参数能够创建代理对象。
当代理对象的方法执行时,会统一交给InvocationHandler的invoke()方法处理,同时将代理对象本身this作为第一个参数传入。

InvocationHandler中的invoke()方法有三个参数

public UserDao invoke(Object proxy, Method method, Object[] args) 
  • 第一个参数Object proxy ,是真正的代理对象,代理对象在执行方法时,会将this作为参数传递给invoke()方法。
    通过该参数可以拿到代理对象身上的信息,也可以将此代理对象作为返回值返回,实现对代理对象的层层调用。
  • 第二个参数Method,是原始方法的Method对象,可以获取原始方法的信息,通过反射来调用原始方法
  • 第三个参数是原始方法的参数

你可能感兴趣的:(java,开发语言,代理模式)