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方法是由谁来调用的,代理对象是怎么生成的,直到前几个星期才把这些问题全部搞明白了. ...
随机推荐
- service和pod通过标签绑定
service和pod绑定 apiVersion: v1 kind: Service metadata: name: my-haproxy labels: app: my-haproxy spec: ...
- docker私有仓库pull/push
相关条件: 登录 配置秘钥
- Python入门-随机漫步
Python入门-随机漫步,贴代码吧,都在代码里面 代码1 class文件 random_walk.py from random import choice class RandomWalk(): # ...
- hadoop distcp 命令& 不同hadoop 版本cp
# 1 版本相同 hadoop distcp -m 10 -bandwidth 150 hdfs://ns1/user/hive/warehouse/public.db/public_oi_fact ...
- hive 索引
hive 有限的支持索引,不支持主键外键,可以对表添加索引,也可以为某个分区添加索引.维护索引也要额外的存储空间和计算资源. 创建索引需要指定索引处理器 如 as 'org.apache.hadoop ...
- mapPartitions
mapPartitions操作与 map类似,只不过映射的参数由RDD中的每一个元素变成了RDD中每一个分区的迭代器,如果映射过程需要频繁创建额外的对象,使用mapPartitions操作要比map操 ...
- DDD领域驱动设计(例子)
参考:https://tech.meituan.com/DDD_in_%20practice.html
- APP-2-Hbuilder开发环境搭建
1.Hbuilder下载 http://www.dcloud.io/hbuilderx.html 2.夜神模拟器下载 https://www.yeshen.com/ 3.chrome浏览器下载 htt ...
- (原)Echarts 报Uncaught Error: Initialize failed: invalid dom 根本解决
1.循环出的Echarts出现 Uncaught Error: Initialize failed: invalid dom ,附上完美解决方案 setTimeout(function () { co ...
- 尚硅谷redis学习8-事务
是什么? 能干嘛? 常用命令 案例说明 1.正常执行 2.放弃事务 3.全部放弃(全体连坐) 4.只抛弃错误(冤头债主) 5.watch监控 悲观锁 悲观锁(Pessimistic Lock), 顾名 ...