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 .提示细节:
- 代理对象的父类java.lang.reflect.Proxy类是实现了java.io.Serializable接口的,所以代理对象都是可序列化的;
- 对于有参和无参方法,都是通过invoke方法调用,无参方法会直接传入null,所以在invoke方法中使用args参数时一定要先进行null的判断;
- 对于原始数据类型(int,boolean等8种),代理对象的方法中参数和返回值都进行了包装和解包装。
- 代理对象生成过程中用到了反射,生成字节码时,反射Object对象方法和反射接口方法;生成实例时,反射获取代理对象的构造器;代理对象方法调用过程中是没有使用反射的。
- 有没有感觉跟 装饰器模式 有一些 异曲同工 呢?
4. 以上就是个人总结分享的JDK动态代理的内容,原创内容,转载请注明出处。
个人水平有限,有误的地方欢迎评论中指正。
byte[]
Java,JDK动态代理的原理分析的更多相关文章
- 解析JDK动态代理实现原理
JDK动态代理使用实例 代理模式的类图如上.关于静态代理的示例网上有很多,在这里就不讲了. 因为本篇讲述要点是JDK动态代理的实现原理,直接从JDK动态代理实例开始. 首先是Subject接口类. p ...
- java jdk动态代理模式举例浅析
代理模式概述 代理模式是为了提供额外或不同的操作,而插入的用来替代”实际”对象的对象,这些操作涉及到与”实际”对象的通信,因此代理通常充当中间人角色. java中常用的动态代理模式为jdk动态代理和c ...
- JDK动态代理深入理解分析并手写简易JDK动态代理(下)
原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-05/27.html 作者:夜月归途 出处:http://www.guitu ...
- JDK动态代理深入理解分析并手写简易JDK动态代理(上)
原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-03/27.html 作者:夜月归途 出处:http://www.guitu ...
- java jdk动态代理学习记录
转载自: https://www.jianshu.com/p/3616c70cb37b JDK自带的动态代理主要是指,实现了InvocationHandler接口的类,会继承一个invoke方法,通过 ...
- Java JDK 动态代理使用及实现原理分析
转载:http://blog.csdn.net/jiankunking 一.什么是代理? 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问.代理类负责为委托类预处理 ...
- Java JDK 动态代理实现和代码分析
JDK 动态代理 内容 一.动态代理解析 1. 代理模式 2. 为什么要使用动态代理 3. JDK 动态代理简单结构图 4. JDK 动态代理实现步骤 5. JDK 动态代理 API 5.1 java ...
- JDK 动态代理实现原理
一.引言 Java动态代理机制的出现,使得Java开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象便能动态生成代理类.代理类会负责将所有方法的调用分派到委托对象上反射执行,在分派执行的过 ...
- JDK动态代理实现原理--转载
之前虽然会用JDK的动态代理,但是有些问题却一直没有搞明白.比如说:InvocationHandler的invoke方法是由谁来调用的,代理对象是怎么生成的,直到前几个星期才把这些问题全部搞明白了. ...
随机推荐
- 设置nginx中文件上传的大小限制度
通过设置nginx的client_max_body_size解决nginx+php上传大文件的问题: 用nginx来做webserver的时,上传大文件时需要特别注意client_max_body_s ...
- Java课程作业之动手动脑(五)
1.请阅读并运行AboutException.java示例. import javax.swing.*; class AboutException { public static void main( ...
- HANA私有云解决方案
在移动互联网时代,不支持在云上的部署一定会落伍的,HANA作为SAP力推的技术,对云的支持也做的很不错,今天我们就来探讨一下HANA私有云解决方案,至于公有云或者混合云,思路也是大同小异了. ...
- kvm云主机使用宿主机usb设备
有些时候KVM客户机还是要使用USB设备,比如USB密钥等 KVM命令行参数 -usb 打开usb驱动程序,启动客户机usb支持-usbdevice devname 为客户机增加usb设备,devna ...
- Loadrunner:win10下Vuser 运行脚本通过,Controller执行用户并发报错
现象:win7安装LR可以正常使用,将win7升级到win10之后,运行场景之后报错:Error (-81024): LR_VUG: The 'QTWeb' type is not supported ...
- 29.如何不用 transition 和 animation 也能做网页动画
原文地址:https://segmentfault.com/a/1190000014964220 感想:动画效果运用了具有滚动效果的marquee标签 HTML代码: 注释: <!-- mar ...
- EXCEL中统计单元格内容出现次数
参考网站: https://jingyan.baidu.com/article/7c6fb428dfcc9580642c90ae.html 统计单元格内容出现次数是工作中经常会涉及到的问题. 那么,如 ...
- Analysis of Web.xml in Hello1project
一下是hello1 web inf 里的 web.xml <?xml version="1.0" encoding="UTF-8"?><w ...
- ubuntu-Linux下如何安装Tensorflow?
http://wiki.jikexueyuan.com/project/tensorflow-zh/get_started/os_setup.html https://www.cnblogs.com/ ...
- 解决 shopnc b2b2c 版权问题 修改路经ULR及目录文件夹思路及教程
相信各位使用过NC的朋友,多多少少收到过律师函,把一堆人吓尿了,原因你使用了盗版,大哥都要吃饭可以理解#网络那么大,他怎么能快速定位到您的,原因很简单 搜索引擎,NC在开发中定义了URL路由规则,在百 ...