JDK 动态代理 源码简单分析
代理的作用就是在访问真实对象之前或者之后可以额外加入一些操作。
JDK 的动态代理 只需要 5 步。
- 真实对象必须要实现接口,首先创建一个接口
public interface HelloWorld {
void sayHellowWorld();
} - 创建真实对象。
public class HelloWorldImpl implements HelloWorld {
@Override
public void sayHellowWorld() {
System.out.println("Hello World !");
}
} - 创建代理类
public class JdkProxySImpleDemo implements InvocationHandler { //代理类必须实现 InvocationHandler 接口 @Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable{ } } - 建立代理对象和真实对象之间的关系。
public class JdkProxySImpleDemo implements InvocationHandler { //真实对象
public Object target = null;
//建立代理对象和真实对象的代理关系,并返回代理对象
public Object bind(Object target){
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException { }
} - 实现代理方法。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
System.out.println("sayHelloWorld 执行前"); //实现代理方法
Object Obj = method.invoke(target,args);
System.out.println("sayHelloWorld 执行后");
return Obj; } - 使用代理对象代理真实对象
public static void main(String[] args) { JdkProxySImpleDemo porxy = new JdkProxySImpleDemo();
HelloWorld helloWorld = (HelloWorld)porxy.bind(new HelloWorldImpl());
helloWorld.sayHellowWorld();
} //执行结果
sayHelloWorld 执行前
Hello World !
sayHelloWorld 执行后
源码分析
1. 创建代理对象 使用的是Porxy类的静态方法 newProxyInstance 他需要三个参数。
- ClassLoader loader 真实对象的类加载器
- Class<?>[] interfaces 真实对象的接口
- 代理对象本身
2 创建一个对象的过程: .java 文件编译为.class 字节码文件,加载字节码文件生成Class对象,Class对象创建 实例对象。

3. 查找或者创建代理类的Class对象,使用 getProxyClass0(loader, intfs); 方法
只有同一个类加载器加载的字节码生成的对象才是同一个对象,这里的传入真实对象的类加载器,一来确定此代理类是否被此加载器加载,二来如果没有加载则指定使用真实对象的类加载器加载代理类。

真实对象实现的接口不能超过65535个。
4. proxyClassCache.get(loader, interfaces); 使用真实对象的类加载器加载代理对象的字节码,如果已经加载过则返回 Class 对象的副本,如果没有加载则使用 ProxyClassFactory 进行加载

这里出现了ConcurrentMap ,所有代理类的Class对象会存储在这。它是一个二级map,最外层map的key就是 类加载器,值也是一个ConcurrentMap ,这个map 的key存放代理类Class 对象,value 是一个Boolean 值 。
这里根据 类加载器 找值 如果为空则进行初始化创建 ConcurrentMap。
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); 生成代理类的字节码并创建代理类Class对象

apply 方法是Proxy类的内部类ProxyClassFactory的方法,它可以生成字节码,并且创建Class对象

首先会检查确保真实对象实现的接口是由同一个类加载器加载的,还会检查是否是接口
检查通过后就会生成代理类对象的字节码 .class

byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); 生成二进制字节码。
生成的字节码保存在缓存中,创建字节码比较耗费性能,所以一次创建好后保存在缓存中下次可以直接使用减少性能开销。
defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length); 这里使用 一个本地方法生成 代理类的Class对象。
5. 生成 代理类的Class对象后 创建一个 Factory 并将 Class对象放入二级ConcurrentMap 中

6.使用生成的代理类的Class对象构造实例对象

c1 就是 代理类的Class对象 ,return cons.newInstance(new Object[]{h}); 创建代理对象的实例;
7. 由于代理类的字节码存在内存中所以无法直接反编译查看,可以使用System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); 把字节码保存到本地


使用反编译工具打开$Ptoxy0.class(IDEA自带反编译工具)

可以看到 代理类 继承自 Proxy 类,实现了被代理类的接口。
这也是为什么被代理对象要实现接口,要想代理必须要有相同的方法替代真实方法执行,一种就是继承被代理类,另一种就是拥有共同接口,由于Java只能单继承所以只能实现共同的接口
8 查看 代理对象的 代理方法 发现它只是做了一个转发

super.h就是 代理对象自己 ,这相当于执行 代理对象重载的invoke方法
Method 对象 m3 = Class.forName("day0826_proxy.HelloWorld").getMethod("sayHellowWorld"); 存储了真实对象方法的信息

最后利用反射 调用真实对象的 方法。动态代理完成
JDK 动态代理 源码简单分析的更多相关文章
- FFmpeg的HEVC解码器源码简单分析:解析器(Parser)部分
===================================================== HEVC源码分析文章列表: [解码 -libavcodec HEVC 解码器] FFmpeg ...
- FFmpeg源码简单分析:libswscale的sws_scale()
===================================================== FFmpeg的库函数源码分析文章列表: [架构图] FFmpeg源码结构图 - 解码 FFm ...
- 利用JDK动态代理机制实现简单拦截器
利用JDK动态代理机制实现简单的多层拦截器 首先JDK动态代理是基于接口实现的,所以我们先定义一个接口 public interface Executer { public Object execut ...
- Django-session中间件源码简单分析
Django-session中间件源码简单分析 settings里有关中间件的配置 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddlew ...
- FFmpeg源码简单分析:结构体成员管理系统-AVOption
===================================================== FFmpeg的库函数源码分析文章列表: [架构图] FFmpeg源码结构图 - 解码 FFm ...
- negroni-gzip源码简单分析解读
negroni-gzip源码简单分析解读 这是一个为Negroni设计的gzip压缩处理中间件,需要用到已有的compress中的gzip,阅读了不长的源码之后,总结了一些关键要点和注意点. 检查是否 ...
- FFmpeg的HEVC解码器源码简单分析:概述
===================================================== HEVC源码分析文章列表: [解码 -libavcodec HEVC 解码器] FFmpeg ...
- FFmpeg的HEVC解码器源码简单分析:解码器主干部分
===================================================== HEVC源码分析文章列表: [解码 -libavcodec HEVC 解码器] FFmpeg ...
- Hessian 源码简单分析
Hessian 是一个rpc框架, 我们需要先写一个服务端, 然后在客户端远程的调用它即可. 服务端: 服务端通常和spring 做集成. 首先写一个接口: public interface Hell ...
随机推荐
- 常量表达式 & constexpr
[常量表达式] 一个这样的表达式:值不会改变 && 在编译过程中就能够得到计算结果 常见的常量表达式:字面值.用常量表达式初始化的const对象 一个对象是不是常量表达式由它的数据类型 ...
- 【转】hexo博客图片问题
1.首先确认_config.yml 中有 post_asset_folder:true. Hexo 提供了一种更方便管理 Asset 的设定:post_asset_folder 当您设置post_as ...
- Chromium学习笔记
1. How to build chromium Follow the steps on:http://www.chromium.org/Home 需要安装Win7 x64的OS,PC的配置尽可能高端 ...
- PHP判断类型的方法
1.gettype():获取变量类型 2.is_array():判断变量类型是否为数组类型 3.is_double():判断变量类型是否为倍浮点类型 4.is_float():判断变量类型是否为浮点类 ...
- 【bzoj3856】Monster 乱搞
题目描述 你要打一只h点血的怪物,每回合你攻击会造成a点伤害,回合结束后怪物会回b点血,你每攻击k回合需要休息一次,该回合不能造成伤害.怪物血量降到0以下就会死亡,问最后能否打死怪物. 输入 Ther ...
- [CF45G]Prime Problem
题目大意:将$1$到$n(1<n\leqslant6000)$分成若干组数,要求每组数的和均为质数,若存在一种分配方式,输出每个数所在的组的编号,有多组解输出任意一组解,若不存在,输出$-1$ ...
- Visio中的Undo和Redo
1.Visio默认Undo和Redo操作是可用的,Appliacation中的UndoEnabled标志Undo和Redo操作是否可用. m_Visio.Window.Application.Undo ...
- BZOJ4869 [Shoi2017]相逢是问候 【扩展欧拉定理 + 线段树】
题目链接 BZOJ4869 题解 这题调得我怀疑人生,,结果就是因为某些地方\(sb\)地忘了取模 前置题目:BZOJ3884 扩展欧拉定理: \[c^a \equiv c^{a \mod \varp ...
- 【NOIP模拟赛】黑红树 期望概率dp
这是一道比较水的期望概率dp但是考场想歪了.......我们可以发现奇数一定是不能掉下来的,因为若奇数掉下来那么上一次偶数一定不会好好待着,那么我们考虑,一个点掉下来一定是有h/2-1个红(黑),h/ ...
- 面试前需要弄懂的SQL
说明:创建数据库 view source print? 1 Create DATABASE database-name 说明:删除数据库 view source print? 1 drop d ...

