(转)细说JDK动态代理的实现原理
原文:http://blog.csdn.net/mhmyqn/article/details/48474815
关于JDK的动态代理,最为人熟知的可能要数Spring AOP的实现,默认情况下,Spring AOP的实现对于接口来说就是使用的JDK的动态代理来实现的,而对于类的代理使用CGLIB来实现。那么,什么是JDK的动态代理呢?
JDK的动态代理,就是在程序运行的过程中,根据被代理的接口来动态生成代理类的class文件,并加载运行的过程。JDK从1.3开始支持动态代理。那么JDK是如何生成动态代理的呢?JDK动态代理为什么不支持类的代理,只支持接口的代理?
首先来看一下如何使用JDK动态代理。JDK提供了java.lang.reflect.Proxy类来实现动态代理的,可通过它的newProxyInstance来获得代理实现类。同时对于代理的接口的实际处理,是一个java.lang.reflect.InvocationHandler,它提供了一个invoke方法供实现者提供相应的代理逻辑的实现。可以对实际的实现进行一些特殊的处理,像Spring AOP中的各种advice。下面来看看如何使用。
被代理的接口
package com.mikan.proxy; /**
* @author Mikan
* @date 2015-09-15 18:00
*/
public interface HelloWorld { void sayHello(String name); }
接口的实现类:
- package com.mikan.proxy;
- /**
- * @author Mikan
- * @date 2015-09-15 18:01
- */
- public class HelloWorldImpl implements HelloWorld {
- @Override
- public void sayHello(String name) {
- System.out.println("Hello " + name);
- }
- }
实现一个java.lang.reflect.InvocationHandler:
- package com.mikan.proxy;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- /**
- * @author Mikan
- * @date 2015-09-15 19:53
- */
- public class CustomInvocationHandler implements InvocationHandler {
- private Object target;
- public CustomInvocationHandler(Object target) {
- this.target = target;
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- System.out.println("Before invocation");
- Object retVal = method.invoke(target, args);
- System.out.println("After invocation");
- return retVal;
- }
- }
使用代理:
- package com.mikan.proxy;
- import java.lang.reflect.Proxy;
- /**
- * @author Mikan
- * @date 2015-09-15 18:01
- */
- public class ProxyTest {
- public static void main(String[] args) throws Exception {
- System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
- CustomInvocationHandler handler = new CustomInvocationHandler(new HelloWorldImpl());
- HelloWorld proxy = (HelloWorld) Proxy.newProxyInstance(
- ProxyTest.class.getClassLoader(),
- new Class[]{HelloWorld.class},
- handler);
- proxy.sayHello("Mikan");
- }
- }
运行的输出结果:
- localhost:classes mikan$ java com/mikan/proxy/ProxyTest
- Before invocation
- Hello Mikan
- After invocation
从上面可以看出,JDK的动态代理使用起来非常简单,但是只知道如何使用是不够的,知其然,还需知其所以然。所以要想搞清楚它的实现,那么得从源码入手。这里的源码是1.7.0_79。首先来看看它是如何生成代理类的:
- public static Object newProxyInstance(ClassLoader loader,
- Class<?>[] interfaces,
- InvocationHandler h)
- throws IllegalArgumentException {
- if (h == null) {
- throw new NullPointerException();
- }
- final Class<?>[] intfs = interfaces.clone();
- final SecurityManager sm = System.getSecurityManager();
- if (sm != null) {
- checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
- }
- // 这里是生成class的地方
- Class<?> cl = getProxyClass0(loader, intfs);
- // 使用我们实现的InvocationHandler作为参数调用构造方法来获得代理类的实例
- try {
- final Constructor<?> cons = cl.getConstructor(constructorParams);
- final InvocationHandler ih = h;
- if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
- return AccessController.doPrivileged(new PrivilegedAction<Object>() {
- public Object run() {
- return newInstance(cons, ih);
- }
- });
- } else {
- return newInstance(cons, ih);
- }
- } catch (NoSuchMethodException e) {
- throw new InternalError(e.toString());
- }
- }
其中newInstance只是调用Constructor.newInstance来构造相应的代理类实例,这里重点是看getProxyClass0这个方法的实现:
- private static Class<?> getProxyClass0(ClassLoader loader,
- Class<?>... interfaces) {
- // 代理的接口数量不能超过65535(没有这种变态吧)
- if (interfaces.length > 65535) {
- throw new IllegalArgumentException("interface limit exceeded");
- }
- // JDK对代理进行了缓存,如果已经存在相应的代理类,则直接返回,否则才会通过ProxyClassFactory来创建代理
- return proxyClassCache.get(loader, interfaces);
- }
其中代理缓存是使用WeakCache实现的,如下
- private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
- proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
具体的缓存逻辑这里暂不关心,只需要关心ProxyClassFactory是如何生成代理类的,ProxyClassFactory是Proxy的一个静态内部类,实现了WeakCache的内部接口BiFunction的apply方法:
- private static final class ProxyClassFactory
- implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
- // 所有代理类名字的前缀
- private static final String proxyClassNamePrefix = "$Proxy";
- // 用于生成代理类名字的计数器
- private static final AtomicLong nextUniqueNumber = new AtomicLong();
- @Override
- public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
- // 省略验证代理接口的代码……
- String proxyPkg = null; // 生成的代理类的包名
- // 对于非公共接口,代理类的包名与接口的相同
- for (Class<?> intf : interfaces) {
- int flags = intf.getModifiers();
- if (!Modifier.isPublic(flags)) {
- String name = intf.getName();
- int n = name.lastIndexOf('.');
- String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
- if (proxyPkg == null) {
- proxyPkg = pkg;
- } else if (!pkg.equals(proxyPkg)) {
- throw new IllegalArgumentException(
- "non-public interfaces from different packages");
- }
- }
- }
- // 对于公共接口的包名,默认为com.sun.proxy
- if (proxyPkg == null) {
- proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
- }
- // 获取计数
- long num = nextUniqueNumber.getAndIncrement();
- // 默认情况下,代理类的完全限定名为:com.sun.proxy.$Proxy0,com.sun.proxy.$Proxy1……依次递增
- String proxyName = proxyPkg + proxyClassNamePrefix + num;
- // 这里才是真正的生成代理类的字节码的地方
- byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
- proxyName, interfaces);
- try {
- // 根据二进制字节码返回相应的Class实例
- return defineClass0(loader, proxyName,
- proxyClassFile, 0, proxyClassFile.length);
- } catch (ClassFormatError e) {
- throw new IllegalArgumentException(e.toString());
- }
- }
- }
ProxyGenerator是sun.misc包中的类,它没有开源,但是可以反编译来一探究竟:
- public static byte[] generateProxyClass(final String var0, Class[] var1) {
- ProxyGenerator var2 = new ProxyGenerator(var0, var1);
- final byte[] var3 = var2.generateClassFile();
- // 这里根据参数配置,决定是否把生成的字节码(.class文件)保存到本地磁盘,我们可以通过把相应的class文件保存到本地,再反编译来看看具体的实现,这样更直观
- if(saveGeneratedFiles) {
- AccessController.doPrivileged(new PrivilegedAction() {
- public Void run() {
- try {
- FileOutputStream var1 = new FileOutputStream(ProxyGenerator.dotToSlash(var0) + ".class");
- var1.write(var3);
- var1.close();
- return null;
- } catch (IOException var2) {
- throw new InternalError("I/O exception saving generated file: " + var2);
- }
- }
- });
- }
- return var3;
- }
saveGeneratedFiles这个属性的值从哪里来呢:
- private static final boolean saveGeneratedFiles = ((Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))).booleanValue();
GetBooleanAction实际上是调用Boolean.getBoolean(propName)来获得的,而Boolean.getBoolean(propName)调用了System.getProperty(name),所以我们可以设置sun.misc.ProxyGenerator.saveGeneratedFiles这个系统属性为true来把生成的class保存到本地文件来查看。
这里要注意,当把这个属性设置为true时,生成的class文件及其所在的路径都需要提前创建,否则会抛出FileNotFoundException异常。如:
- Exception in thread "main" java.lang.InternalError: I/O exception saving generated file: java.io.FileNotFoundException: com/sun/proxy/$Proxy0.class (No such file or directory)
- at sun.misc.ProxyGenerator$1.run(ProxyGenerator.java:336)
- at sun.misc.ProxyGenerator$1.run(ProxyGenerator.java:327)
- at java.security.AccessController.doPrivileged(Native Method)
- at sun.misc.ProxyGenerator.generateProxyClass(ProxyGenerator.java:326)
- at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:672)
- at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:592)
- at java.lang.reflect.WeakCache$Factory.get(WeakCache.java:244)
- at java.lang.reflect.WeakCache.get(WeakCache.java:141)
- at java.lang.reflect.Proxy.getProxyClass0(Proxy.java:455)
- at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:738)
- at com.mikan.proxy.ProxyTest.main(ProxyTest.java:15)
- at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
- at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
- at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
- at java.lang.reflect.Method.invoke(Method.java:606)
- at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
即我们要在运行当前main方法的路径下创建com/sun/proxy目录,并创建一个$Proxy0.class文件,才能够正常运行并保存class文件内容。
反编译$Proxy0.class文件,如下所示:
- package com.sun.proxy;
- import com.mikan.proxy.HelloWorld;
- 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 HelloWorld {
- private static Method m1;
- private static Method m3;
- private static Method m0;
- private static Method m2;
- public $Proxy0(InvocationHandler paramInvocationHandler) {
- super(paramInvocationHandler);
- }
- public final boolean equals(Object paramObject) {
- try {
- return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
- }
- catch (Error|RuntimeException localError) {
- throw localError;
- }
- catch (Throwable localThrowable) {
- throw new UndeclaredThrowableException(localThrowable);
- }
- }
- public final void sayHello(String paramString) {
- try {
- this.h.invoke(this, m3, new Object[] { paramString });
- return;
- }
- catch (Error|RuntimeException localError) {
- throw localError;
- }
- catch (Throwable localThrowable) {
- throw new UndeclaredThrowableException(localThrowable);
- }
- }
- public final int hashCode() {
- try {
- return ((Integer)this.h.invoke(this, m0, null)).intValue();
- }
- catch (Error|RuntimeException localError) {
- throw localError;
- }
- catch (Throwable localThrowable) {
- throw new UndeclaredThrowableException(localThrowable);
- }
- }
- public final String toString() {
- try {
- return (String)this.h.invoke(this, m2, null);
- }
- catch (Error|RuntimeException localError) {
- throw localError;
- }
- catch (Throwable localThrowable) {
- throw new UndeclaredThrowableException(localThrowable);
- }
- }
- static {
- try {
- m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
- m3 = Class.forName("com.mikan.proxy.HelloWorld").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });
- m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
- m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
- return;
- }
- catch (NoSuchMethodException localNoSuchMethodException) {
- throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
- }
- catch (ClassNotFoundException localClassNotFoundException) {
- throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
- }
- }
- }
可以看到,动态生成的代理类有如下特性:
- 继承了Proxy类,实现了代理的接口,由于java不能多继承,这里已经继承了Proxy类了,不能再继承其他的类,所以JDK的动态代理不支持对实现类的代理,只支持接口的代理。
- 提供了一个使用InvocationHandler作为参数的构造方法。
- 生成静态代码块来初始化接口中方法的Method对象,以及Object类的equals、hashCode、toString方法。
- 重写了Object类的equals、hashCode、toString,它们都只是简单的调用了InvocationHandler的invoke方法,即可以对其进行特殊的操作,也就是说JDK的动态代理还可以代理上述三个方法。
- 代理类实现代理接口的sayHello方法中,只是简单的调用了InvocationHandler的invoke方法,我们可以在invoke方法中进行一些特殊操作,甚至不调用实现的方法,直接返回。
至此JDK动态代理的实现原理就分析的差不多了。同时我们可以想像一下Spring AOP提供的各种拦截该如何实现,就已经很明了了,如下所示:
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- // BeforeAdvice
- Object retVal = null;
- try {
- // AroundAdvice
- retVal = method.invoke(target, args);
- // AroundAdvice
- // AfterReturningAdvice
- }
- catch (Throwable e) {
- // AfterThrowingAdvice
- }
- finally {
- // AfterAdvice
- }
- return retVal;
- }
上面是对于Spring AOP使用JDK动态代理实现的基本框架代码,当然具体的实现肯定比这个复杂得多,但是基本原理不外乎如是。所以理解基本原理对于理解其他的代码也是很有好处的。
(转)细说JDK动态代理的实现原理的更多相关文章
- JDK动态代理的实现原理
学习JDK动态代理,从源码层次来理解其实现原理参考:http://blog.csdn.net/jiankunking/article/details/52143504
- jdk动态代理使用及原理
jdk动态代理的使用 1.创建实现InvocationHandler接口的类,实现invoke(Object proxy, Method method, Object[] args)接口,其中invo ...
- JDK动态代理案例与原理分析
一.JDK动态代理实现案例 Person接口 package com.zhoucong.proxy.jdk; public interface Person { // 寻找真爱 void findlo ...
- jdk动态代理与cglib代理、spring aop代理实现原理
原创声明:本博客来源与本人另一博客[http://blog.csdn.net/liaohaojian/article/details/63683317]原创作品,绝非他处摘取 代理(proxy)的定义 ...
- jdk动态代理与cglib代理、spring aop代理实现原理解析
原创声明:本博客来源为本人原创作品,绝非他处摘取,转摘请联系博主 代理(proxy)的定义:为某对象提供代理服务,拥有操作代理对象的功能,在某些情况下,当客户不想或者不能直接引用另一个对象,而代理对象 ...
- 何为代理?jdk动态代理与cglib代理、spring Aop代理原理浅析
原创声明:本博客来源为本人原创作品,绝非他处摘取,转摘请联系博主 代理(proxy)的定义:为某对象提供代理服务,拥有操作代理对象的功能,在某些情况下,当客户不想或者不能直接引用另一个对象,而代理对象 ...
- 代理模式(静态代理、JDK动态代理原理分析、CGLIB动态代理)
代理模式 代理模式是设计模式之一,为一个对象提供一个替身或者占位符以控制对这个对象的访问,它给目标对象提供一个代理对象,由代理对象控制对目标对象的访问. 那么为什么要使用代理模式呢? 1.隔离,客户端 ...
- jdk动态代理与cglib代理、spring Aop代理原理-代理使用浅析
原创声明:本博客来源为本人原创作品,绝非他处摘取,转摘请联系博主 代理(proxy)的定义:为某对象提供代理服务,拥有操作代理对象的功能,在某些情况下,当客户不想或者不能直接引用另一个对象,而代理对象 ...
- 解析JDK动态代理实现原理
JDK动态代理使用实例 代理模式的类图如上.关于静态代理的示例网上有很多,在这里就不讲了. 因为本篇讲述要点是JDK动态代理的实现原理,直接从JDK动态代理实例开始. 首先是Subject接口类. p ...
随机推荐
- POJ2516 Minimum Cost【最小费用最大流】
题意: 有N个客户,M个仓库,和K种货物.已知每个客户需要每种货物的数量,每个仓库存储每种货物的数量,每个仓库运输各种货物去各个客户的单位费用.判断所有的仓库能否满足所有客户的需求,如果可以,求出最少 ...
- include的作用
#include发生在预处理阶段,整个编译链接过程,#include是最简单的了,没有之一.就是在include的位置直接把文件原原本本完完整整一字不落的包含进来,下面举一个极端点的例子: //fil ...
- More Effective C++ 条款0,1
More Effective C++ 条款0,1 条款0 关于编译器 不同的编译器支持C++的特性能力不同.有些编译器不支持bool类型,此时可用 enum bool{false, true};枚举类 ...
- Android 常用 adb 命令总结【转】
原文链接 针对移动端 Android 的测试, adb 命令是很重要的一个点,必须将常用的 adb 命令熟记于心, 将会为 Android 测试带来很大的方便,其中很多命令将会用于自动化测试的脚本当中 ...
- SpringMVC与Struts不同(五)
springmvc与struts2不同 1.springmvc的入口是一个servlet即前端控制器,而struts2入口是一个filter过滤器. 2.springmvc是基于方法开发(一个url对 ...
- host-only
https://www.cnblogs.com/yaox/p/6635312.html
- Netty+SpringBoot写一个基于Http协议的文件服务器
本文参考<Netty权威指南> NettyApplication package com.xh.netty; import org.springframework.boot.SpringA ...
- Java探针-Java Agent技术-阿里面试题
Java探针参考:Java探针技术在应用安全领域的新突破 最近面试阿里,面试官先是问我类加载的流程,然后问了个问题,能否在加载类的时候,对字节码进行修改 我懵逼了,答曰不知道,面试官说可以的,使用Ja ...
- python 历险记(四)— python 中常用的 json 操作
目录 引言 基础知识 什么是 JSON? JSON 的语法 JSON 对象有哪些特点? JSON 数组有哪些特点? 什么是编码和解码? 常用的 json 操作有哪些? json 操作需要什么库? 如何 ...
- centos常用网络管理命令
网卡配置命令:ifconfig (ip addr , ip link) ifconfig:显示所有活动状态的相关信息 ifconfig Interface:仅显示指定接口的相关信息 ifc ...