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.
     *
     * 

{@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, s, is present * and any of the following conditions is met: *

    *
  • 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;
  • *
  • 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};
  • *
  • 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.
  • *
* @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的动态代理还是设计的很精妙的,值得一探究竟。欢迎评论,不喜勿喷。

你可能感兴趣的:(java,开发语言)