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者,在这个地方难免会懵,不急,我们循序向下观看分析。
在测试类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;
这个怎么赋上值的呐?
刚才我们看了,在动态代理类对象生成的时候调用的是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的动态代理还是设计的很精妙的,值得一探究竟。欢迎评论,不喜勿喷。