jdk动态代理原理源码深度分析

jdk动态代理原理源码深度分析

简单实例

接口:

package com.example.demo0423.proxy;

public interface ProxyTest {
    void send(String msg);
}

实现类:

package com.example.demo0423.proxy;

public class ProxyTestImpl implements ProxyTest{
    @Override
    public void send(String msg) {
        System.out.println(msg);
    }
}

InvocationHandler实现类:

package com.example.demo0423.proxy;

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

public class ProxyTestInvocationHandlerImpl implements InvocationHandler {
    private ProxyTest proxyTest;

    public void setProxyTest(ProxyTest proxyTest) {
        this.proxyTest = proxyTest;
    }

    @Override
    public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
        System.out.println("前置会话");
        Object object = method.invoke(proxyTest,args);
        System.out.println("后置会话");
        return object;
    }
}

MainTest运行测试类,查看动态代理效果:

package com.example.demo0423.proxy;

import java.lang.reflect.Proxy;

public class MainTest {
    public static void main(String[] args) {
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        ProxyTestInvocationHandlerImpl invocationHandler=new ProxyTestInvocationHandlerImpl();
        ProxyTest proxyTestInstance=new ProxyTestImpl();
        invocationHandler.setProxyTest(proxyTestInstance);
        ProxyTest   proxyTest=  (ProxyTest) Proxy.newProxyInstance(proxyTestInstance.getClass().getClassLoader(),
                proxyTestInstance.getClass().getInterfaces(),invocationHandler);
        proxyTest.send("hello");
    }
}

运行结果:

"C:\Program Files\Java\jdk1.8.0_271\bin\java.exe" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:52267,suspend=y,server=n -javaagent:D:\idea2019\ideaIU-2019.2.4.win\plugins\java\lib\rt\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_271\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_271\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_271\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_271\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_271\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_271\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_271\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_271\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_271\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_271\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_271\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_271\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_271\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_271\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_271\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_271\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_271\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_271\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_271\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_271\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_271\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_271\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_271\jre\lib\rt.jar;D:\workspace-ideal-20220423\demo01\target\classes;D:\maven-Repository\org\springframework\boot\spring-boot-starter\2.6.7\spring-boot-starter-2.6.7.jar;D:\maven-Repository\org\springframework\boot\spring-boot\2.6.7\spring-boot-2.6.7.jar;D:\maven-Repository\org\springframework\spring-context\5.3.19\spring-context-5.3.19.jar;D:\maven-Repository\org\springframework\boot\spring-boot-autoconfigure\2.6.7\spring-boot-autoconfigure-2.6.7.jar;D:\maven-Repository\org\springframework\boot\spring-boot-starter-logging\2.6.7\spring-boot-starter-logging-2.6.7.jar;D:\maven-Repository\ch\qos\logback\logback-classic\1.2.11\logback-classic-1.2.11.jar;D:\maven-Repository\ch\qos\logback\logback-core\1.2.11\logback-core-1.2.11.jar;D:\maven-Repository\org\apache\logging\log4j\log4j-to-slf4j\2.17.2\log4j-to-slf4j-2.17.2.jar;D:\maven-Repository\org\apache\logging\log4j\log4j-api\2.17.2\log4j-api-2.17.2.jar;D:\maven-Repository\org\slf4j\jul-to-slf4j\1.7.36\jul-to-slf4j-1.7.36.jar;D:\maven-Repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;D:\maven-Repository\org\springframework\spring-core\5.3.19\spring-core-5.3.19.jar;D:\maven-Repository\org\springframework\spring-jcl\5.3.19\spring-jcl-5.3.19.jar;D:\maven-Repository\org\yaml\snakeyaml\1.29\snakeyaml-1.29.jar;D:\maven-Repository\org\springframework\boot\spring-boot-starter-web\2.6.7\spring-boot-starter-web-2.6.7.jar;D:\maven-Repository\org\springframework\boot\spring-boot-starter-json\2.6.7\spring-boot-starter-json-2.6.7.jar;D:\maven-Repository\com\fasterxml\jackson\core\jackson-databind\2.13.2.1\jackson-databind-2.13.2.1.jar;D:\maven-Repository\com\fasterxml\jackson\core\jackson-annotations\2.13.2\jackson-annotations-2.13.2.jar;D:\maven-Repository\com\fasterxml\jackson\core\jackson-core\2.13.2\jackson-core-2.13.2.jar;D:\maven-Repository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.13.2\jackson-datatype-jdk8-2.13.2.jar;D:\maven-Repository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.13.2\jackson-datatype-jsr310-2.13.2.jar;D:\maven-Repository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.13.2\jackson-module-parameter-names-2.13.2.jar;D:\maven-Repository\org\springframework\boot\spring-boot-starter-tomcat\2.6.7\spring-boot-starter-tomcat-2.6.7.jar;D:\maven-Repository\org\apache\tomcat\embed\tomcat-embed-core\9.0.62\tomcat-embed-core-9.0.62.jar;D:\maven-Repository\org\apache\tomcat\embed\tomcat-embed-el\9.0.62\tomcat-embed-el-9.0.62.jar;D:\maven-Repository\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.62\tomcat-embed-websocket-9.0.62.jar;D:\maven-Repository\org\springframework\spring-web\5.3.19\spring-web-5.3.19.jar;D:\maven-Repository\org\springframework\spring-beans\5.3.19\spring-beans-5.3.19.jar;D:\maven-Repository\org\springframework\spring-webmvc\5.3.19\spring-webmvc-5.3.19.jar;D:\maven-Repository\org\springframework\spring-aop\5.3.19\spring-aop-5.3.19.jar;D:\maven-Repository\org\springframework\spring-expression\5.3.19\spring-expression-5.3.19.jar;D:\maven-Repository\org\springframework\boot\spring-boot-devtools\2.6.7\spring-boot-devtools-2.6.7.jar;D:\maven-Repository\org\projectlombok\lombok\1.18.24\lombok-1.18.24.jar;D:\maven-Repository\org\slf4j\slf4j-api\1.7.36\slf4j-api-1.7.36.jar;D:\idea2019\ideaIU-2019.2.4.win\lib\idea_rt.jar" com.example.demo0423.proxy.MainTest
Connected to the target VM, address: '127.0.0.1:52267', transport: 'socket'
前置会话
hello
后置会话
Disconnected from the target VM, address: '127.0.0.1:52267', transport: 'socket'

Process finished with exit code 0

运行原理分析

如果看客阁下对上述demo例子还觉得运行结果有点懵逼的话,或者觉得这个demo神奇的话,那就跟当年鄙人初学动态代理时应该是一个水准的,那这篇文章下面的这段就挺适合阁下静下心来看看

动态代理精华分析

从上述demo中,可以看到,demo用到了接口ProxyTest,接口实现ProxyTestImpl,还有个InvocationHandler实现类ProxyTestInvocationHandlerImpl。然后调用Proxy的newProxyInstance方法,获取到了一个代理对象,调用这个代理对象的send方法,然后看运行效果是即调用了invocationHanderImpl的invoke方法,也调用了ProxyTestImpl的send方法。如果初学动态代理,或者初学java者,在这个地方难免会懵,不急,我们循序向下观看分析。

类Proxy-创建动态代理对象

在测试类MainTest,调用Proxy类的接口生成动态代理类:


Proxy.newProxyInstance(proxyTestInstance.getClass().getClassLoader(),
                proxyTestInstance.getClass().getInterfaces(),invocationHandler);

那么,我们不妨进入这个newProxyInstance方法去看看它到底做了什么,Proxy类是jdk封装的类,在此我只把这个方法黏贴进来,因为这个类太长了:

/**
     * Returns an instance of a proxy class for the specified interfaces
     * that dispatches method invocations to the specified invocation
     * handler.
     *
     * <p>{@code Proxy.newProxyInstance} throws
     * {@code IllegalArgumentException} for the same reasons that
     * {@code Proxy.getProxyClass} does.
     *
     * @param   loader the class loader to define the proxy class
     * @param   interfaces the list of interfaces for the proxy class
     *          to implement
     * @param   h the invocation handler to dispatch method invocations to
     * @return  a proxy instance with the specified invocation handler of a
     *          proxy class that is defined by the specified class loader
     *          and that implements the specified interfaces
     * @throws  IllegalArgumentException if any of the restrictions on the
     *          parameters that may be passed to {@code getProxyClass}
     *          are violated
     * @throws  SecurityException if a security manager, <em>s</em>, is present
     *          and any of the following conditions is met:
     *          <ul>
     *          <li> the given {@code loader} is {@code null} and
     *               the caller's class loader is not {@code null} and the
     *               invocation of {@link SecurityManager#checkPermission
     *               s.checkPermission} with
     *               {@code RuntimePermission("getClassLoader")} permission
     *               denies access;</li>
     *          <li> for each proxy interface, {@code intf},
     *               the caller's class loader is not the same as or an
     *               ancestor of the class loader for {@code intf} and
     *               invocation of {@link SecurityManager#checkPackageAccess
     *               s.checkPackageAccess()} denies access to {@code intf};</li>
     *          <li> any of the given proxy interfaces is non-public and the
     *               caller class is not in the same {@linkplain Package runtime package}
     *               as the non-public interface and the invocation of
     *               {@link SecurityManager#checkPermission s.checkPermission} with
     *               {@code ReflectPermission("newProxyInPackage.{package name}")}
     *               permission denies access.</li>
     *          </ul>
     * @throws  NullPointerException if the {@code interfaces} array
     *          argument or any of its elements are {@code null}, or
     *          if the invocation handler, {@code h}, is
     *          {@code null}
     */
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

从代码中我们可以看到第三个参数InvocationHandler h,最终通过下面一行代码传了出去:

cons.newInstance(new Object[]{h});

那cons这个变量是什么?看newProxyInstance方法中其中有几行:

/*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);
final Constructor<?> cons = cl.getConstructor(constructorParams);
cons.newInstance(new Object[]{h});

通过上面几行代码能看出来,getProxyClass0是获取代理类的class对象的,这获取代理类class对象过程中如果没有字节码文件就生成代理类$Proxy0.class字节码文件了(getProxyClass0这个方法如何生成字节码文件,这个小伙伴们可以自行进入这个方法阅读里面的代码,太长了,不想贴出来,大家仔细看应该都能看懂这里面的东西,提示一下,最终生成字节码文件调用的还是proxy的内部类ProxyClassFactory的apply方法生成的),然后调用代理类的getConstructor方法获取构造器类的class对象,最后用构造器的newInstance方法反射创建一个代理对象。

查看动态代理类源代码

关于如何查看动态代理类,请仔细看MainTest类里main方法第一行,有一行代码:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

代码运行的时候加入这行,就会在项目的根目录下生成一个包com\sun\proxy,动态代理类就会生成在这个目录下(注意这个目录和java的classpath不是一回事,这个是在项目的根目录下,和pom.xml和src目录平级的)。

动态代理类反编译后源代码

至此,有点思路了,Proxy代理类的作用是生成代理类,并把invocationhandler实例传给代理类,那么代理类长什么样?下面我把我这个demo生成的动态代理类反编译出来的源代码贴出来:

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

package com.sun.proxy;

import com.example.demo0423.proxy.ProxyTest;
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 ProxyTest {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    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 void send(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    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"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.example.demo0423.proxy.ProxyTest").getMethod("send", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

通过代码可以看到,这个动态代理类, extends Proxy implements ProxyTest ,所以mainTest里拿到动态代理类后,调用send方法,其实是调用的是动态代理类的send方法:

 ProxyTestInvocationHandlerImpl invocationHandler=new ProxyTestInvocationHandlerImpl();
        ProxyTest proxyTestInstance=new ProxyTestImpl();
        invocationHandler.setProxyTest(proxyTestInstance);
        ProxyTest   proxyTest=  (ProxyTest) Proxy.newProxyInstance(proxyTestInstance.getClass().getClassLoader(),
                proxyTestInstance.getClass().getInterfaces(),invocationHandler);

        proxyTest.send("hello");

而动态代理类的send方法我们看:

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

就一行,super.h.invoke(this, m3, new Object[]{var1}),这个地方这个super.h
是什么呐?
点进去看,super.h是父类中的:

    protected InvocationHandler h;

这个怎么赋上值的呐?

动态代理类对象运行时调用invoke方法源码分析

刚才我们看了,在动态代理类对象生成的时候调用的是cons.newInstance(new Object[]{h}),没看明白的话回过去再看看,
而生成动态代理对象的是时候的这个h是Proxy.newProxyInstance调用时候传过来的,也就是说这个h就是main方法里new的那个invocationHandler,如下所示的

ProxyTestInvocationHandlerImpl invocationHandler=new ProxyTestInvocationHandlerImpl();

这个handler最终被传到动态代理类的构造器中反射生成动态代理对象,然后我们看动态代理对象的构造函数:

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

这个构造器又调用了父类的构造函数,它的父类是谁呐?Proxy,所以我们接着看Proxy的构造器:

    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }

看明白了吧,最终invocationHandler被传到Proxy的h上了。
那么当调用动态代理类的send方法时,super.h其实就是下面这个对象:

ProxyTestInvocationHandlerImpl invocationHandler=new ProxyTestInvocationHandlerImpl();

所以super.h.invoke就是调用的ProxyTestInvocationHandlerImpl 里的invoke方法:

    @Override
    public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
        System.out.println("前置会话");
        Object object = method.invoke(proxyTest,args);
        System.out.println("后置会话");
        return object;
    }

而这个方法里的参数obj是动态代理对象,Method是要调用的send方法对象,args是调用时候传输的参数对象数组,这些参数都是从动态代理类里获取到传输给invocationHandler的。
平时我们设计动态代理都会把要代理的类作为类属性传输进来让invoker方法去反射调用,就像本例中把ProxyTestImpl的实例传进来用于调用被代理方法,但是如果我们有被代理方法调用需求的话,其实可以不设计接口的实现类,直接让动态代理去生成实现类,然后根据接口名字去做一些操作,像mybatis底层框架就是这么做的。
jdk的动态代理还是设计的很精妙的,值得一探究竟。欢迎评论,不喜勿喷。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值