JDK动态代理入门

介绍

要了解动态代理,首先要知道什么是代理,在笔者看来,代理的功能概括来说就是:在不修改被代理类代码的情况下给这个类包含的方法增加某些功能
**至于为什么要不修改这个被代理类?**可能是为了满足开闭原则,可能是想把这个被代理类给隐藏起来,等等 。原因可能有很多,但概括起来就是不修改代理类。

要增加什么功能呢?(后文提到的增强指的就是增加功能)
这个就看使用者怎么写了,这个是非常灵活的,常用的就是增加日志统计方法耗时

我熟知的动态代理通常有两种,即JDK动态代理CGLIB动态代理

笔者最近学习了JDK动态代理,所以编写这个博客输出一下,因为笔者也是初学,了解的不多,尽可能写的详细~
好处是,笔者知道初学者的学习的难点在哪,应该更能帮助初学者理解~
坏处嘛,嘿嘿,如果教错了,那就抱歉啦~

JDK动态代理

笔者前面前面提到过JDK动态代理是基于接口来实现的,具体怎么个实现法,参考下面这张图。
代理类实现了与被代理类相同的接口,那么相应的代理类要实现对应的接口方法
既然是要增强被代理类某些方法的功能,那么思考一下,代理类实现接口方法时方法中应当包含那些内容?
答案显而易见:实现新增功能的代码 + 调用被代理类对应的方法

在这里插入图片描述

比如我想统计一个方法的执行时间,那么伪代码如下

void test (Object[] args){
	1. 统计开始时间
	2. 调用被代理类的test方法
	3. 统计结束时间
	4. 计算整体耗时
}

鲁迅曾经说过:光说理论不给代码,那就是在耍流氓(鲁迅:也许我没说过?

客官稍安勿躁,细想一下要实现如上的功能我们需要解决的两个问题:

  • 代理对象怎么创建?
  • 增强功能肯定是要写代码的,这个代码写在哪?

代理对象怎么创建

非常非常简单,java.lang.reflect中有一个类Proxy, 这个类中有个静态方法Proxy.newProxyInstance(),通过这个方法我们可以获取某个对象的代理对象。

啪一下就蹦出来个陌生的方法就, 问你怕不怕? 别怕,慢慢来~

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

这个静态方法有三个参数需要提供(这个静态方法还有一个重载方法,我们不需要关注)

  • ClassLoader loader : 被代理对象的类加载器
  • Class<?>[] interfaces: 被代理对象实现的接口
  • InvocationHandler h :一个InvocationHandler接口的实现类

问:为什么需要被代理对象的类加载器?
答:所谓动态代理,动态的点就在于JVM会帮我们生成代理类的字节码文件并加载,然后实例化,这过程就需要类加载器ClassLoader,为了和被代理对象保持一致,所以就使用被代理对象的类加载器进行加载。

问:为什么需要被代理对象的接口集合?
答:这个接口本质上就是告诉代理对象,哪些方法是我想要增强的。 也就是说,提供的所有接口中的所有方法,就是JVM帮我们创建的代理对象所能代理的方法,也就是我们可以对其进行功能增强的方法。

这也意味着,并非需要把被代理类实现的所有接口传递进去,仅仅需要将包含我们想要增强方法的接口传递进去即可。
如被代理类 Test实现了两个接口,分别是接口A,接口B, 现在我想增强接口A中的方法,这个时候,传递进去的接口集合中仅仅需要接口A。

问:这个InvocationHandler接口是个什么东东?
答:这个就涉及到我们前面提到的第二个问题:新增功能的代码我们要写在哪里?, 答案就是这个InvocationHandler里 ,InvocationHandler接口中只有一个方法需要我们实现,那就是invoke方法,准确的说,我们要新增的代码功能,就是写在这个invoke方法里的。
Object invoke(Object proxy, Method method, Object[] args)
客官先别急,这个方法具体怎么用我们后面会提到,现在我们只需要有一个认知:我们想要增强的功能是写在invoke这个方法里的,因此我们需要创建一个类实现InvocationHandler接口并实现其中的invoke方法,并将这个类当做参数传给newProxyInstance方法

理清了这三个参数,成功调用Proxy.newProxyInstance方法,我们就可以获取到代理类对象。

举例

举个具体的例子:

接口:

public interface JDKTest {
    void hello();
    String eat(String food);
}

被代理类:

public class JDKTestImpl implements JDKTest{


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

    @Override
    public String eat(String food) {
        return "吃了" + food;
    }
}

InvocationHanlder实现类:

package com.zp.myproject.demo;


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {
    private Object target;

    public MyInvocationHandler(Object target){
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method.getName()+"实现了代理增强");
        return method.invoke(target,args);
    }

}

测试类:


import java.lang.reflect.Proxy;

public class TEST {
    public static void main(String[] args) {
        System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
        JDKTestImpl JDKTestImpl = new JDKTestImpl();
        Object JDKProxy = Proxy.newProxyInstance(
                JDKTestImpl.getClass().getClassLoader(),
                JDKTestImpl.getClass().getInterfaces(),
                new MyInvocationHandler(JDKTestImpl)
        );

        JDKTest jdkTest = (JDKTest) JDKProxy;
        System.out.println(jdkTest.eat("Apple"));
        jdkTest.hello();

    }
}

这个例子中,我们的接口为JDKTest,它有一个实现类JDKTestImpl,也就是我们上面提到的被代理类,需要增强的类

同时我们实现了InvocationHandler接口 MyInvocationHandler,里面包含一个Object target, 这就就是我们想要代理的实例,同时我们实现了invoke方法

Test中我们实例化了JDKImpl,并将其作为target传给MyInvocationHandler

Proxy.newInstance()这个方法的返回值JDKProxy就是JVM为我们创建的代理对象,它是一个Object类型,但是我们可以将其强转为JDKTest类型,因为它实现了JDKTest这个接口。

JVM为其生成了对应的字节码文件, System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true"); 这行代码可以让JVM将生成的字节码文件保存到本地,这样我们就可以反编译出源码直接查看

运行一下,可以看到结果符合预期,两个方法都进行了增强

在这里插入图片描述


现在我们来好好看看这个代理对象的源码,看看是怎么个事儿

创建出的代理类源码

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package jdk.proxy1;

import com.zp.myproject.demo.JDKTest;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

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

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

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

    public final boolean equals(Object var1) {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() {
        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 String eat(String var1) {
        try {
            return (String)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

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

    static {
        ClassLoader var0 = $Proxy0.class.getClassLoader();

        try {
            m0 = Class.forName("java.lang.Object", false, var0).getMethod("hashCode");
            m1 = Class.forName("java.lang.Object", false, var0).getMethod("equals", Class.forName("java.lang.Object", false, var0));
            m2 = Class.forName("java.lang.Object", false, var0).getMethod("toString");
            m3 = Class.forName("com.zp.myproject.demo.JDKTest", false, var0).getMethod("eat", Class.forName("java.lang.String", false, var0));
            m4 = Class.forName("com.zp.myproject.demo.JDKTest", false, var0).getMethod("hello");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

    private static MethodHandles.Lookup proxyClassLookup(MethodHandles.Lookup var0) throws IllegalAccessException {
        if (var0.lookupClass() == Proxy.class && var0.hasFullPrivilegeAccess()) {
            return MethodHandles.lookup();
        } else {
            throw new IllegalAccessException(var0.toString());
        }
    }
}

源码分析

在这里插入图片描述
首先,可以看到这个代理类的名称叫做$Proxy0,它实现了JDKTest接口并继承了Proxy


其次,可以看到它有一个有参构造方法,传入了一个InvocationHandler接口实现类, 并在方法内部调用了super(var1)

我们来看一下父类中怎么写的

在这里插入图片描述
父类中仅有两个操作,判断h非空,将这个h赋值给类的属性h,而这个InvocationHandler对象,实际就是我们之前创建代理类时传入的MyInvocationHander


看回代理对象,它还有5个Method类型的属性,命名m0 ~ m4 ,从下图可以看出,在静态代码块中,利用了反射机制,对m0 ~ m4 进行赋值
在这里插入图片描述

  • m0 : hashCode()
  • m1: equals(Obeject o)
  • m2: toString()
  • m4: eat()
  • m5: hello()

发现了什么! 前三个是所有Java类都有的方法 hashCode() equals(Object) toString(), 而后两个是JDKTest接口中对应的方法。

那么这些个方法有什么用呢? ,我们再看下去。

接下来我们看看代理类中实现的接口方法里面到底都写了些什么

在这里插入图片描述
嗯哼? 有点怪? 发现了什么?代理类本身并没有进行功能增强,而是调用了InvocationHandler.invoke()!所以我们前面说的,具体的增强功能是写在invoke方法里面,现在是否能理解了?

现在我们可以回过头来结合super.h.invoke(this, m4, (Object[]{var1}),看看这个invoke方法的三个参数分别代表了什么。

Object invoke(Object proxy, Method method, Object[] args)

  • Method method: 这个代表了当前被代理的方法
  • Object[] args: 这个代表了当前被代理方法的参数
  • Object proxy: 这个代表了被代理类本身, 传入的是this

在这里插入图片描述
所以现在能看懂MyInvocationHandlerinvoke的编写逻辑了吗,它遵照的就是我在开篇写的伪代码的逻辑,做了两件事

  • 调用被代理对象的方法
  • 实现增强功能的逻辑

因为要调用被代理对象的方法,所以,我们的MyInvocationHandler中必须包含被代理对象, 这也就是target字段的作用

OK?method和args我理解了,那么还有一个问题:这个proxy有什么用,为什么要把代理对象传递给invoke方法呢?

其实这个问题我也没有太清楚,但是能想到的一个原因就是:保证增强功能的时候调用其他方法时这个被调用的方法也能增强。

比如在增强方法A,在编写增强逻辑时,我想要调用方法B,这个时候我就有两个选择:

  1. 使用被代理对象,调用target.B()
  2. 使用代理对象, 调用proxy.B()

这取决我想要调用的是没有增强的B方法还是增强后的B方法, 如果我想调用增强的B方法,那么就调用prxoy.B(),反之调用target.B()

如果我的增强逻辑是记录所有方法的调用日志,那么如果我在增强代码中调用B方法时选择target.B(),那么这个方法时没有增强过的,也就是这次调用的日志是没有的,反之,如果我们调用proxy.B(),那么这次调用的日志同样能被记录。 不知道这样你们是否能理解

用例子说明:

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method.getName() + "实现了代理增强");
        if (method.getName().equals("eat")) {
            ((JDKTest) proxy).hello();
        }
        return method.invoke(target,args);
    }
    

如果这么写, 增强eat()方法时调用了增强的hello(),那么输出是这样的:

在这里插入图片描述


躺若我调用没有增强的hello()

在这里插入图片描述
那么结果是这样的

在这里插入图片描述


总结

JDK动态代理 通过反射的机制生成了代理类, 这个代理类实现了被代理类的接口,以及通过反射机制获取了被代理对象需要增强的方法, 当用户使用代理类的方法时,它会调用InvocationHandler.invoke(),将信息转发给inovke(), 并在invoke()中完成增强逻辑以及调用被代理对象的方法。

所以大致是一下这个图,如果客官能看懂下面这个图,那么说明你的理解程度至少和我是一个水平了(骄傲.jpg)
在这里插入图片描述

笔者第一次写这么长的博客文章,如果对客官有帮助,可以点个赞吗~非常感谢,当然,我需要强调的是:
笔者也是现学现卖,说不上精通熟练,所以如果有讲的错误的地方,我非常抱歉,也欢迎发现问题的大佬能够及时指出问题,我尽快改正,避免祸害了其他的萌新

  • 31
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值