什么是代理

在计算机编程领域,代理(Proxy)是一个类或对象,它充当其他对象的接口。代理通常控制着对于真实对象的访问,并允许在调用真实对象之前或之后执行额外的操作。

代理能用来做啥

  1. 控制对对象的访问

    • 代理可以控制客户端对真实对象的访问,允许在访问前后执行额外的操作。这种控制可以包括权限验证、安全性检查、日志记录等,从而确保访问真实对象的合法性和安全性。
  2. 增强真实(目标)对象的功能

    • 代理可以在访问真实对象时,为其添加额外的功能,比如延迟加载、缓存、验证和过滤输入、性能优化等。这种方式使得代理可以在不修改真实对象的情况下,扩展或增强其功能。

总结:对目标对象的功能实现了扩展 但是无侵入,符合Java开发规范,极大的降低了代码之间的耦合度

 **最经典的spring中的AOP就是通过代理模式实现的**

代理模式分为:

  • 静态代理
  • 动态代理
    1.面向接口的jdk动态代理模式
    2.面向继承的cglib代理模式

对于代理模式的更多基础请自行学习,我只做简单概述,本篇只对实现以及源码分析进行讲解

代码实现/举例:

面向接口的jdk动态代理模式的实现:(实现功能:为业务层代码进行功能增强)

需求:为目标类的指定方法进行功能增强/功能扩充
第一步:写出业务接口以及实现类

业务接口:OrderBiz

//目标类接口
public interface OrderBiz {
    public int updateOrder(int orderId,int money);	//更新订单
    public void saveOrder(int orderId);			   //保存订单
    public void showOrder();					  //获取订单信息
    public void findAllOrders();				  //获取所有订单
}

实现类OrderBizImpl

//目标类接口实现
public class OrderBizImpl implements OrderBiz{
    @Override
    public int updateOrder(int orderId, int money) {
        System.out.println("updateOrder,修改订单:"+orderId+"金额为:"+money);
        return 1;
    }

    @Override
    public void saveOrder(int orderId) {
        System.out.println("saveOrder,保存订单:"+orderId);
    }

    @Override
    public void showOrder() {
        System.out.println("showOrder");
    }

    @Override
    public void findAllOrders() {
        System.out.println("findAllOrders");
    }
}
第二步:实现回调处理器接口

回调处理器接口 CheckRightsInvocationHandler

//回调处理器接口
public class CheckRightsInvocationHandler implements InvocationHandler {

    // TODO:  目标类的引用
    private Object target;

    //TODO:目标类的引用以及回调处理接口的初始化
    public CheckRightsInvocationHandler(Object target) {
        this.target = target;
    }

    //对外提供一个方法 用于创建一个   基于目标类的  代理类对象
    public Object createProxy() {
        //TODO:jdk中提供了一个Proxy.newProxyInstance() 来创建代理类对象
        //TODO:ClassLoader loader:  可以通过目标类的 ClassLoader 来获取,如 target.getClass().getClassLoader()。
        //TODO:Class<?>[] interfaces:生成的代理类需要实现的接口列表,如果目标类实现了多个接口,你可以通过 target.getClass().getInterfaces() 来获取所有接口的列表
        //TODO:InvocationHandler h:  InvocationHandler 接口只有一个方法 invoke(),在代理对象的方法被调用时调用该方法,允许你在方法调用前后进行额外的处理。
        Object proxy = Proxy.newProxyInstance(CheckRightsInvocationHandler.class.getClassLoader(),
                target.getClass().getInterfaces(), //目标类所有的接口     //一个类实现了多个接口,那么所有的接口都可以被代理
                this);

        //TODO:this表明:一个代理对象被调用时,由jvm 自动回调this所指的InvocationHandler对象的   invoke()

        return proxy; //这个proxy就是一个代理类对象  

    }
    //这个方法,至关重要
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//        1.什么时候调用 -->以下方法执行时调用
        if (method.getName().startsWith("update") ||
                method.getName().startsWith("save") ||
                method.getName().startsWith("show")) {

            checkRights();//增强方法(权限认证)
        }
        //        2.调用目标类的目标方法
        Object result = method.invoke(target, args);    //TODO:target.method( args );

        if (method.getName().startsWith("update") ||
                method.getName().startsWith("save") ||
                method.getName().startsWith("show")) {

            recordLogs();   //增强方法(日志信息)
        }

        return result;
    }

    // 权限认证   这是一个增强的功能。。。
    private void checkRights() {
        System.out.println("权限认证   一万行");
    }
    // 记录日志   这是一个增强的功能。。。
    private void recordLogs() {
        System.out.println("日志信息   一万行");
    }

}
第三步:测试类
public class TestMain {
    public static void main(String[] args) {
        
        
        //配置环境变量:将Proxy生成的代理类的字节码保留下来 jdk11适用
        System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles","true");
        //jdk8适用
         //System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");


        //目标对象
        OrderBiz target = new OrderBizImpl();

        //通过InvocationHandler类的对象  创建 代理类的实例
        CheckRightsInvocationHandler handler = new CheckRightsInvocationHandler(target);
        OrderBiz proxy = (OrderBiz) handler.createProxy();          //生成代理对象

//        客户端调的是代理类,实际掉的是目标类

        // 使用代理对象执行目标类的方法(间接执行并没有直接掉用目标类的方法)
        proxy.updateOrder(1001, 100); //TODO:调用代理对象的被代理方法时,jvm  自动回调invoke()  invoke()又会调用目标类的方法
        proxy.showOrder();
        proxy.saveOrder(2);
        proxy.findAllOrders();
    }
}
结果分析:

运行结果:

权限认证   一万行
updateOrder,修改订单:1001金额为:100
日志信息   一万行
权限认证   一万行
showOrder
日志信息   一万行
权限认证   一万行
saveOrder,保存订单:2
日志信息   一万行
findAllOrders

通过运行结果我们就能发现,我们现在已经在updateOrder,showOrder,saveOrder方法上就实现了功能增强了,我们在不改变原有方法的基础上。为这些方法增加了权限认证和日志信息的打印(模拟)了。其实就是在回调接口的invoke()方法中处理的。
写到这里,我们难免会有些疑问?为什么我调用代理类的方法,就会间接的调用目标类的方法呢?这个间接过程是怎么实现的呢?

动态代理的源码分析

回到我们的测试代码

    //目标对象
    OrderBiz target = new OrderBizImpl();

    //通过InvocationHandler类的对象  创建 代理类的实例
    CheckRightsInvocationHandler handler = new CheckRightsInvocationHandler(target);
    OrderBiz proxy = (OrderBiz) handler.createProxy();          //生成代理对象
    //        客户端调的是代理类,实际掉的是目标类
    // 使用代理对象执行代理类的代理方法(间接执行并没有直接掉用目标类的方法)
    proxy.updateOrder(1001, 100); //TODO:调用代理对象的被代理方法时,jvm  自动回调invoke()  invoke()又会调用目标类的方法
	proxy.showOrder();
     proxy.saveOrder(2);
     proxy.findAllOrders();

前三句代码不需要做过多解析,请自行理解。从第四句代码开始,请注意注释。

// 使用代理对象执行代理类的代理方法(间接执行并没有直接掉用目标类的方法)

//调用代理对象的被代理方法时,jvm 自动回调invoke() invoke()又会调用目标类的方法

关键词:间接执行,没有直接调用 ,jvm自动回调,这个地方是最难理解的。多说无妨,直接上调试
在这里插入图片描述

在这里插入图片描述

两个问题:
第一,我们在执行 proxy.updateOrder(1001, 100); 之后,程序竟然直接跑到了回调处理器CheckRightsInvocationHandler的invoke()方法。完全是无迹可寻的。

第二,这个代理对象的名字虽然就是我们的目标对象,但是它的类型却非常奇怪:$proxy@0 是啥东西啊。而且你会发现,对于这个代理对象,你是根本就没办法找到它的。

来看看chatGPT的解释:

在Java中,动态代理对象的具体类是在运行时动态生成的,并不会像普通的Java类一样在编译期间就存在于代码中。这意味着你无法像普通类一样直接查看和访问动态代理类的源代码或者其完整定义

动态代理对象是通过 Proxy.newProxyInstance() 方法创建的,这个方法接受一些参数,包括要代理的接口、一个 InvocationHandler 接口的实现对象等。当你使用动态代理时,实际的代理类是在运行时通过 Java 反射机制动态生成的,通常是基于目标接口的。

由于这种动态生成的特性,你在代码中看到的是代理对象的接口类型,而不是代理对象的具体类。代理对象的具体实现和逻辑是由生成的代理类和代理实现类来完成的,这些类并不会直接暴露给你的代码或者IDE来查看。

动态代理对象实际上是在运行时通过 Java 的反射机制动态生成的类。这些生成的类并不是预先定义在你的代码中,而是在程序运行时根据你提供的接口和处理器(InvocationHandler)来动态生成的。

具体来说:

  • 生成位置:动态代理类是在 Java 虚拟机内部或者特定的类加载器中动态生成的。它们通常不会以普通类的形式存在于你的项目源代码或者编译后的类文件中。
  • 生成过程:当你调用 Proxy.newProxyInstance() 方法时,Java 运行时根据提供的接口和处理器,在内存中生成一个新的类,这个类实现了你指定的接口,并在调用接口方法时委托给你提供的 InvocationHandler 实现。

因此,动态代理对象存在于程序运行时的内存中,而不是静态地存在于你的代码文件或者类文件中。如果你需要了解代理对象的具体行为或状态,通常可以通过调试工具、日志记录或者动态调试信息来获取更多信息。

总结:动态代理对象存在于程序运行时的内存中,而不是静态地存在于你的代码文件或者类文件中。

问题的关键此时已经显露出来了,能拿到代理类的源码,问题就能迎刃而解了。但是很不幸,因为动态代理对象只存在于内存中,我们是无法直接获取的。

但是既然已经看到了这里,我们不能让一切都戛然而止。请回到测试类中,有着这么一行代码:

System.setProperty(“jdk.proxy.ProxyGenerator.saveGeneratedFiles”,“true”);

这行代码的作用是告诉 Java 虚拟机在生成动态代理类的时候,将生成的类文件保存到磁盘上,而不是仅在内存中动态生成并加载。

如果你细心的话,你会发现,在我们的项目路径下,会生成一个com的文件夹,这个文件夹中保存的就是动态代理对象的源码了:
在这里插入图片描述

注:此段代码想要生效,确保插件里的Java Bytecode Decompiler(字节码反编译器)已开启

下面就是这个基于目标类的动态代理对象的源码部分:

public final class $Proxy0 extends Proxy implements OrderBiz {
    private static Method m1;
    private static Method m4;
    private static Method m2;
    private static Method m3;
    private static Method m5;
    private static Method m6;
    private static Method m0;

    //虚拟机内存里的代理对象
    /*
    var1,实际上就是我们的回调处理器接口
    */
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);	//将这个回调处理器接口给父类Proxy,暂存至h变量
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
    /*
    代理类实例调用代理类的代理方法时, super.h.invoke(this, m4, (Object[])null);
    super.h.invoke  ,实际上就是回调的 回调处理器接口CheckRightsInvocationHandler  的invoke()方法,
    此时再回到回调处理器接口CheckRightsInvocationHandler
    整个过程就串起来了
    */
    public final void findAllOrders() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

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

    
    public final int updateOrder(int var1, int var2) throws  {
        try {
            return (Integer)super.h.invoke(this, m3, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    public final void saveOrder(int var1) throws  {
        try {
            super.h.invoke(this, m5, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

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

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m4 = Class.forName("com.yc.jdkproxy.OrderBiz").getMethod("findAllOrders");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.yc.jdkproxy.OrderBiz").getMethod("updateOrder", Integer.TYPE, Integer.TYPE);
            m5 = Class.forName("com.yc.jdkproxy.OrderBiz").getMethod("saveOrder", Integer.TYPE);
            m6 = Class.forName("com.yc.jdkproxy.OrderBiz").getMethod("showOrder");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

ps:对于代理类的具体生成过程不做过多解释,本篇主在理清回调的过程是如何实现的

逐步解析代码:

动态代理类继承自Proxy类,实现了业务接口

public final class $Proxy0 extends Proxy implements OrderBiz

静态代码块:

代理类被加载时执行,并且在静态代码块中初始化了一些静态成员变量 m0m6,这些变量是代表对应方法的 Method 对象。静态成员变量的初始化是在类加载时进行的,因此静态代码块也会在这个时候执行。注意这些都是目标类的方法的对象,涉及到反射

static {
    try {
        m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
        m6 = Class.forName("com.yc.jdkproxy.OrderBiz").getMethod("findAllOrders");
        m2 = Class.forName("java.lang.Object").getMethod("toString");
        m5 = Class.forName("com.yc.jdkproxy.OrderBiz").getMethod("updateOrder", Integer.TYPE, Integer.TYPE);
        m3 = Class.forName("com.yc.jdkproxy.OrderBiz").getMethod("showOrder");
        m4 = Class.forName("com.yc.jdkproxy.OrderBiz").getMethod("saveOrder", Integer.TYPE);
        m0 = Class.forName("java.lang.Object").getMethod("hashCode");
    } catch (NoSuchMethodException var2) {
        throw new NoSuchMethodError(var2.getMessage());
    } catch (ClassNotFoundException var3) {
        throw new NoClassDefFoundError(var3.getMessage());
    }
}

构造方法:

参数:回调处理其接口。执行super(var1); ctrl+左键追踪,将回到处理器接口h交给父类Proxy管理,见下图:

public $Proxy0(InvocationHandler var1) throws  {
    super(var1);
}

在这里插入图片描述

代理类的代理方法

 public final int updateOrder(int var1, int var2) throws  {
        try {
            return (Integer)super.h.invoke(this, m3, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

重点!重点!重点!

return (Integer)super.h.invoke(this, m3, new Object[]{var1, var2});

还记得在测试代码中的一句话吗?代理对象 执行 代理类的 代理方法(间接执行并没有直接掉用目标类的方法),我们再来看这一行代码。super.h是什么,不就是我们执行代理类的构造方法,给它的父类传递的回调处理器接口CheckRightsInvocationHandler吗? 而super.h.invoke()不就是回调的它里面的invoke()方法吗?

原来如此啊,

当我们创建了动态代理对象后,会保留回调处理器接口的一个实例,并将目标类的方法,通过反射生成方法的Method对象。并且会生成对应的代理类的方法,这些方法和目标类的方法对应。当我们调用代理类的方法时,实际上是回调的回调处理器接口InvocationHandler的invoke方法,并将目标方法的反射对象传过去。然后在invoke()方法中,对方法功能进行增强,并通过这个方法的反射对象,保证目标方法的执行不会被影响。

到此,本篇结束,如有不足,恳请批评指正。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐