Java,JDK动态代理的原理分析
1. 代理基本概念:
以下是代理概念的百度解释:代理(百度百科)
总之一句话:三个元素,数据--->代理对象--->真实对象;复杂一点的可以理解为五个元素:输入数据--->代理对象--->真实对象--->代理对象--->输出数据。
2. JDK的动态代理概念:
JDK的动态代理和正常的代理逻辑有些区别。
首先先明确一下术语:类 class ,接口 interface。
JDK动态代理是基于 interface 创建的,而不是真正的对象;也就是说,即使没有真正的对象,JDK依然可以创建代理对象。下面用代码来解释:
public class JDKProxy implements InvocationHandler{ public Object getObject(TestInterface ref){ return Proxy.newProxyInstance(getClass().getClassLoader(), ref.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } }
当然,实际中使用的情况是有真正的对象的,像下面这样:
public class JDKProxy implements InvocationHandler{ TestInterface ref; public Object getObject(TestInterface ref){ this.ref = ref; return Proxy.newProxyInstance(getClass().getClassLoader(), ref.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("doBefore"); Object o = method.invoke(ref, args); System.out.println("doAfter"); return o; } }
那么,和正常的代理逻辑区别就在这里了,JDK的动态代理多依赖一个元素,就是被代理对象ref所实现的接口。如果ref对象没有实现任何接口,那么这个对象是无法被代理的。
那么问题来了: 为什么Java自带的动态代理 选择 要基于接口 ?基于什么考虑,或者说Java如果 选择 直接 代理真正的对象会有什么问题?
3. 进入正题:JDK动态代理是如何实现的?(基于JDK1.8)
Java中涉及到的关键先生:InvocationHandler , Proxy
3.1 使用方法及参数详细解释
代码使用方法:
//代理类,实现InvocationHandler
public class JDKProxy implements InvocationHandler{ private UserService userServiceRef;
//获取代理对象 public UserService getProxy(UserService userServiceRef){ this.userServiceRef = userServiceRef;
//Proxy生成代理对象 return (UserService) Proxy.newProxyInstance(getClass().getClassLoader(), userServiceRef.getClass().getInterfaces(), this); }
//代理对象做的事 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("someone is logining"); Object returnObject = method.invoke(userServiceRef, args); System.out.println("someone login success"); return returnObject; } }
下面是InvocationHandler的接口描述:
package java.lang.reflect; public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
其中的参数:
proxy: 代理对象本身,也就是 getProxy(UserService userServiceRef) 获取到的对象。大家思考一下,为什么要把这个代理对象作为参数传进来?
我个人觉得这是个完全没有必要的参数。
method:userServiceRef中的方法。
args: method方法的参数。
再来看一下Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法参数:
loader:类加载器,用来加载代理类的,即Proxy.newProxyInstance()的返回结果的类字节码。
interfaces:代理对象所实现的接口。 这里接口是数组参数,通常被代理只实现一个接口。那实现多个接口时使用代理对象有什么问题?其实也没问题,就是调用不同接口的方法前需要先强转为对应的接口类,麻烦。
h:实现InvocationHandler的类,也就是示例代码中的JDKProxy类 。
测试代码:
public static void main(String[] args) { UserService userService = new UserServiceImpl(); JDKProxy proxy = new JDKProxy(); UserService userServiceProxy = proxy.getProxy(userService); userServiceProxy.login(); }
3.2 实现的原理与细节:
1.代理对象的创建过程:
创建代理对象的方法:Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
通过这个方法的参数其实可以看到一些眉目,loader用来加载代理类字节码,interfaces作为代理类实现的接口,h为代理对象实际调用的方法(即invoke方法)。
创建过程大致分为几步:
-
- 从缓存中获取代理对象,获取到则直接返回;
缓存由WeakCache中的ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map来存储;
为什么是两级Map? 想一想,Java类的唯一性由ClassLoader+Class决定,所以Key Object是ClassLoader,Key Object是所有接口组成的对象().
WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
其中KeyFactory的作用就是将interfaces转换为Key Object 。
-
- 生成代理对象的类名proxyName;
由 private static final class ProxyClassFactory 来完成.
private static final String proxyClassNamePrefix = "$Proxy"; //ProxyClassFactory
// 每次使用时 自增1
private static final AtomicLong nextUniqueNumber = new AtomicLong();//ProxyClassFactory
ReflectUtil.PROXY_PACKAGE = "com.sun.proxy";//RelectionUtil
proxyName = PROXY_PACKAGE + proxyClassNamePrefix + nextUniqueNumber;
-
- 生成proxyName类的字节码;
由 sun.misc.ProxyGenerator.generateProxyClass(String proxyName, Class<?>[] interfaces, int accessFlags) ,方法返回二进制字节码。
参数: 类名,需要实现的接口,访问标志。
-
- 将字节码加载到虚拟机中,即方法区内存(jdk1.7之前是永久代,jdk8之后的元数据区);
由 java.lang.reflect.Proxy.defineClass0(ClassLoader loader, String proxyName, byte[] proxyClass, int offset, int length) 完成。
private static native Class<?> defineClass0(ClassLoader loader, String name, byte[] b, int off, int len);
这是一个本地方法,通过JNI调用,返回代理的Class对象。
-
- 生成代理Class对象的实例;构造器实例化
生成代理对象的三个参数中的 interfaces, classLoader都使用过了,还有一个InvocationHandler h 没有使用。
通过反射获取代理Class的参数为InvocationHandler的构造器,通过 Constructor.newInstance(new Object[]{h}); 返回最后的代理实例对象。
创建代理的过程就完成了。
2. 代理对象的字节码分析:
还是以上面3.1的例子分析,测试代码稍作修改,如下:
public static void main(String[] args) { System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); //生成的代理对象字节码保存到.class文件中。 JDKProxy proxy = new JDKProxy(); proxy.getProxy(new UserServiceImpl()); //生成代理对象 }
运行测试代码之后,user.dir目录下会多出一个目录:com/sun/proxy,打开后可以看到$Proxy0.class文件。
jd-gui反编译该class文件:为方便阅读,我把方法里的try catch全部移掉了。
package com.sun.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import myproxy.UserService; public final class $Proxy0 extends Proxy implements UserService { private static Method m1; private static Method m2; private static Method m3; private static Method m0; public $Proxy0(InvocationHandler paramInvocationHandler){ super(paramInvocationHandler); //h引用在父类Proxy中 } public final boolean equals(Object paramObject){ return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue(); } public final String toString(){ return (String)this.h.invoke(this, m2, null); } public final boolean login(){ return ((Boolean)this.h.invoke(this, m3, null)).booleanValue(); //boolean存在包装和解包装 } public final int hashCode(){ return ((Integer)this.h.invoke(this, m0, null)).intValue(); } static{ m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m3 = Class.forName("myproxy.UserService").getMethod("login", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); return; } }
一目了然。主要五部分内容:
代理对象继承了Proxy类,并实现了目标接口。
生成了以InvocationHandler为参数的构造器,实例化时将我们的自定JDKProxy(实现了InvocationHandler,并持有被代理对象)传递进去;
生成了Object中的三个方法:equals, hashCode, toString;
生成了接口中的所有方法,全部调用InvocationHandler对象的invoke方法;
生成了对应方法的Method对象属性,传递给invoke方法。
3 .提示细节:
-
- 对于有参和无参方法,都是通过invoke方法调用,无参方法会直接传入null,所以在invoke方法中使用args参数时一定要先进行null的判断;
- 对于原始数据类型(int,boolean等8种),代理对象的方法中参数和返回值都进行了包装和解包装。
- 代理对象生成过程中用到了反射,生成字节码时,反射Object对象方法和反射接口方法;生成实例时,反射获取代理对象的构造器;代理对象方法调用过程中是没有使用反射的。
- 有没有感觉跟 装饰器模式 有一些 异曲同工 呢?
4. 以上就是个人总结分享的JDK动态代理的内容,原创内容,转载请注明出处。
转自https://www.cnblogs.com/selfchange/p/9814290.html