从源码分析JDK动态代理

简介: 从源码分析JDK动态代理

引言


动态代理非常的重要,虽然我们在日常的工作中没有非常底层的 编写过动态代理的代码,但是动态代理却起着非常重要的功能,想一下我们经常使用的框架: 日志框架、AOP等等,所以,如果我们需要看一些框架的源码的时候,还是需要非常熟悉的掌握动态代理的原理。


基本的使用这里就不在介绍了,前面的的博客已经介绍过了《java动态代理》,本篇博客主要解决下面几个问题:


1、JDK动态代理基本原理


2、如何调用到我们自己的myInvoerCationHandler中的invoke方法


3、 被代理的类位为什么要实现接口?


例子

package com.jack.springmvc.proxy.JDK;
public interface MapperI {
    void  jdbcOperate();
}
class  Mapper implements  MapperI{
    @Override
    public void jdbcOperate() {
        System.out.println("执行数据库连接操作");
    }
}
package com.jack.springmvc.proxy.JDK;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInovercationHandler implements InvocationHandler {
    private Object target;
    public MyInovercationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("数据库连接前");
        //执行目标对象的目标方法
        method.invoke(target,args);
        System.out.println("数据库连接后");
        return null;
    }
}
package com.jack.springmvc.proxy.JDK;
import java.lang.reflect.Proxy;
public class Test {
    public static void main(String[] args) throws  Exception {
        Mapper mapper = new Mapper();
        MyInovercationHandler myInovercationHandler = new MyInovercationHandler(mapper);
        MapperI o = (MapperI)Proxy.newProxyInstance(Mapper.class.getClassLoader(), Mapper.class.getInterfaces(), myInovercationHandler);
        o.jdbcOperate();
        System.out.println(o.getClass());
//        ProxyClassUtils.createClassFile(MapperI.class);
    }
}

1、基本原理

20210222164616818.png


我们运行上面的测试方法,得到的是MapperI的一个代理对象,class com.sun.proxy.$Proxy0,执行o.jdbcOperate();这一句代码的基本流程,其实就是上图中的执行流程。为什么会按照和这个流程执行代码呢,在后面我们将生成的代理类的内容输出到文件中,我们打开看看这个类的内容就完全明白了。


2、源码执行流程

20210222171016419.png


3、代理类的内容


我们都知道动态代理生成的代理里我们在项目中看不到的,因为它保存中内存中,所以我们需要编写代码输出到代码内存中。我们从源代码中能看到下面代码:

    if (proxyPkg == null) {
                // if no non-public proxy interfaces, use com.sun.proxy package
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }
            /*
             * Choose a name for the proxy class to generate.
             */
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;
            /*
             * Generate the specified proxy class.
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);

最后 一个方法中已经得到了代理类的字节数组,所以我们可以模拟这个方法将生成的代理内输入到文件中。

import java.io.FileOutputStream;
public class ProxyClassUtils {
    public static void  createClassFile(Class clazz) throws  Exception{
        String className="$Proxy0";
        byte[] classFile = ProxyGenerator.generateProxyClass(
                className, new Class[]{clazz});
        String path ="C:\\project\\luban\\springmvc\\src\\main\\java\\com\\jack\\springmvc\\proxy\\JDK";
        FileOutputStream out = new FileOutputStream(path + className + ".class");
        out.write(classFile);
        out.flush();
        out.close();
    }
}

开头的代码中 ProxyClassUtils.createClassFile(MapperI.class) 就可以将内容输出到文件中。代理类内容如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import com.jack.springmvc.proxy.JDK.MapperI;
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 MapperI {
    private static Method m1;
    private static Method m2;
    private static Method m0;
    private static Method m3;
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
    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);
        }
    }
    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 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);
        }
    }
    public final void jdbcOperate() throws  {
        try {
            super.h.invoke(this, m3, (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"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m3 = Class.forName("com.jack.springmvc.proxy.JDK.MapperI").getMethod("jdbcOperate");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

我们看到了生成的代理类的内容,我们就能明白非常多的事情了


1、我们看到了生成的代理类继承了Proxy,但是java是单继承的机制,所以如果代理类和被代理类之间产生关联, 只能通过实现同一个接口的方式,这就是为什么,jdk代理要求被代理类必须实现接口。


2、我们还看到了一个核心的方法 jdbcOperate(),这个方法就是我们被代理类实现的接口中的方法,所以我们生成的代理类可以调用和被代理类同一个方法。


这个方法中有一个核心的代码:super.h.invoke(this, m3, (Object[])null);  这里面有几个参数:


1、h.invoke()中的 h 是什么呢,我们从源码中可以看到,cons.newInstance(new Object[]{h});  h就是我们传入的自己定义的 MyInovercationHandler ,看到这个参数我们也就 明白了,为什么我们调用生成的 代理类的jdbcOperate()方法会进入到我们自己定义的MyInovercationHandler 中。


2、m3 我们从代理类的末端可以看到m3 其实就是 我们接口中的方法,看到这我们也就能明白在我们自己的定义的MyInovercationHandler中通过method.invoke(target,args)可以调用目标对象的目标方法了。


分析到这我们也就充分的解答了 开头我们提出的几个问题, 重要的是我们将动态生成的代理类从内存中拿到本地自己看一看,就会明白了很多原来不是很懂的地方,这也是看源码的快乐。

目录
相关文章
|
2月前
|
存储 安全 Java
【JDK 源码分析】ConcurrentHashMap 底层结构
【1月更文挑战第27天】【JDK 源码分析】ConcurrentHashMap 底层结构
|
10天前
|
设计模式 Java 程序员
java动态代理(JDK和cglib)
java动态代理(JDK和cglib)
|
12天前
|
设计模式 Java 程序员
java动态代理(JDK和cglib)
java动态代理(JDK和cglib)
10 0
|
13天前
|
缓存 Java Maven
JDK 动态代理
JDK 动态代理
5 0
|
16天前
|
存储 算法 安全
JDK源码分析-HashMap
JDK源码分析-HashMap
|
18天前
|
Java Spring
深入解析Spring源码,揭示JDK动态代理的工作原理。
深入解析Spring源码,揭示JDK动态代理的工作原理。
18 0
|
2月前
|
设计模式 Java
JDK动态代理
JDK动态代理
|
2月前
|
Java 程序员 API
浅谈JDK动态代理
浅谈JDK动态代理
46 1
|
2月前
|
设计模式 Java API
[Java]静态代理、动态代理(基于JDK1.8)
本篇文章主要是对静态代理和动态代理实现思路的简述,以示例为主,少涉及理论。 如果文中阐述不全或不对的,多多交流。
64 1
[Java]静态代理、动态代理(基于JDK1.8)
|
2月前
|
存储 数据库
享元模式、在 JDK-Interger 的应用源码分析
享元模式(案例解析)、在 JDK-Interger 的应用源码分析