从源码分析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、基本原理

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

2、源码执行流程

                      

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)可以调用目标对象的目标方法了。

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

猜你喜欢

转载自blog.csdn.net/u013045437/article/details/113942896