动态代理源码分析

动态代理

代理模式的解释:为其他对象提供一种代理以控制对这个对象的访问,增强一个类中的某个方法,对程序进行扩展。

比如,现在存在一个UserService类:

public class UserService  {

	public void test() {
		System.out.println("test...");
	}

}

此时,我们new一个UserService对象,然后执行test()方法,结果是显而易见的。

如果我们现在想在不修改UserService类的源码前提下,给test()增加额外逻辑,那么就可以使用动态代理机制来创建UserService对象了,比如:

// 被代理的对象target
UserService target = new UserService();

// 通过cglib技术
Enhancer enhancer = new Enhancer();
// 设置代理哪个类UserService
enhancer.setSuperclass(UserService.class);

// 设置方法拦截器 定义额外逻辑,也就是代理逻辑
enhancer.setCallbacks(new Callback[]{new MethodInterceptor() {
	@Override
    // 当前代理对象正在执行的方法method,入参objects 这里是空
	public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
		System.out.println("before...");
		Object result = methodProxy.invoke(target, objects);
        method.invoke(target,)
		System.out.println("after...");
		return result;
	}
}});

// 动态代理所创建出来的UserService对象
UserService userService = (UserService) enhancer.create();

// 执行这个userService的test方法时,就会额外会执行一些其他逻辑
userService.test();

得到的都是UserService对象,但是执行test()方法时的效果却不一样了,这就是代理所带来的效果。

上面是通过cglib来实现的代理对象的创建,是基于父子类的,被代理类(UserService)是父类,代理类是子类,代理对象就是代理类的实例对象,代理类是由cglib创建的,对于程序员来说不用关心。

CGLIB源码分析

阅读代理

这个对应的Method对象就是UserService这个类的test方法

cglib在58行会拿到那个方法拦截器然后在65行执行方法拦截器的intercept方法。

一开始肯定拿到是个空,所以在60行才会获得真正的方法拦截器,传入的参数是代理对象

传进来的就是UserService的代理对象,然后强转成代理对象,一开始进去if就把false改为true,就不再进来了

ThreadLocal

从当前线程ThreadLocal拿到CALLBACKWIntercept过滤器,然后给属性赋值

后续就可以使用,执行他的intercept方法

在这个方法给ThreadLocal赋值的,那这个方法在哪调用的?

在各种newInstance里面

=============================================================================

newInstance()

newInstance()这个方法在CGLIB提供的Factory接口提供的。相当于某个代理对象UserService实现了Factory工厂

相当于可以拿这个Factory生成另一个代理对象。【用得少】

=============================================================================

其实ThreadLocal都在这个Ehancer的create()方法里设置的

Enhancer是个增强器,目前只是设置了要去产生代理对象的参数。

利用参数去创建代理对象

一开始把传进去的参数封装成对象key【包含了要代理的参数 等下要进行缓存 存到个map里去的】

这个key对应的value就是最后所产生的代理类不是代理对象

根据设置的代理参数,去缓存一个代理类【不可能每次调用都去产生一个代理类 因为参数不会变化】

所以后续create就直接从缓存拿到代理类。

紧接着调用create方法,最终返回结果就是代理对象了

这里传进去的obj就是我们刚才所生成的代理类,

然后传进来的代理类就会执行firstInstance()方法

根据代理类创建我们的代理对象

传进的参数是我们刚才new Enhancer()里面设置的Callback和代理类

这个是我们刚才找的Callback方法

一旦执行这个方法,就会设置进ThreadLocal里面【代理类生成后,代理对象生成之前】

小总结:

传入代理类和你在Enhancer里面设置的方法拦截器传进去,

然后把刚才那个设置ThreadLocal的方法名字,和代理类传进去,找到Method对象,

然后用反射,因为是static,所以obj是null,把方法拦截器座位参数callbacks传进去

最后就成功设置到ThreadLocal这个属性上面去

有值之后,接下来就会产生代理对象,代理对象执行test方法

就会拿到刚才设置的Callback,然后执行Callback里的方法拦截器方法,从而执行到我们所设置的intercept方法里面去

代理类怎么产生的?

这里的obj就是代理类

默认会去找缓存,如果设置不开缓存

那每次都会走gennerate方法去生成代理类。

设置了缓存,直接从缓存里get

拿到了就直接返回。

没有拿到就走createEntry方法。最终还是会调用到生成代理类的那个generate方法

apply()方法

来看看cglib是怎么生成代理类的?

Generate()方法

如果有设置superClass就设置代理类前缀

底层用ASM技术,生成具体的类的名字,构造方法,属性啊 ,类里面各种各样的方法啊【直接去生成字节码 这里其实是经过idea反编译之后 这里其实是生产字节码的指令】

大总结

①首先通过Ehancer对象设置要代理的类【supperClass】,拦截器方法【methodInterceptor】,然后调用create方法,根据这些参数生成一个Object类型的key【包含了要代理的参数 等下要进行缓存 存到个map里去的】这个key对应的value就是最后所产生的代理类不是代理对象】根据设置的代理参数,去缓存一个代理类【不可能每次调用都去产生一个代理类 因为参数不会变化】然后调用create()方法。

代理类哪里来的?

默认会去找缓存,如果设置不开缓存,那每次都会走gennerate方法去生成代理类。如果有设置superClass就设置代理类前缀,底层是用ASM技术,会生成具体的类的名字,构造方法,属性啊 ,类里面各种各样的方法啊【直接去生成字节码 这里其实是经过idea反编译之后 这里其实是生产字节码的指令】

②create()方法里面,会传入代理类【从缓存取或者gennerate】并执行firstInstance()方法。然后会调用代理类的static方法去setThreadLocal【3个参数 new Enhancer()里面设置的方法拦截器和代理类和设置代理类的ThreadLocal的方法名字】,然后用反射,最后就成功把方法拦截器设置到ThreadLocal这个属性上面去。然后根据代理类创建我们的代理对象

最后执行代理对象的test()方法然后就会拿到刚才ThreadLocal里设置的Callback,然后执行Callback里的方法拦截器方法,从而执行到我们所设置的intercept方法里面去,实现对方法的增强

=============

MethodProxy

methodProxy代理的就是你当前正在执行的方法method

代理类里其实是有2个test的方法的,执行test就会执行增强逻辑,

执行CGLIB$test$4就不会执行增强逻辑【直接执行UserService的test方法】。

method其实代理了两个方法【名字是固定的,test方法和CGLIB$test$4方法】

invoke他就只会执行test()方法,invokeSuper就会执行CGLIB$test$4()方法,他不关心你传的对象里有没有这个方法

第二个会报错,因为被代理的对象里头没有CGLIB$test$4这个方法。

但是实际上报错是报的不能转换

换成对象o【(即代理类的实例),这个类是目标类的子类】就能正常执行不报错,相当于执行super.test()只会执行一次。

这个会报错,25行又会执行增强的test方法,里面又会执行方法拦截器,相当于递归了。

MethodProxy对象什么时候创建的?

对象在这里传进去的

代理类生成的时候里面的静态代码块构造出来的

test方法和增强的test方法返回值都是void,所以传一个就好了

构造MethodProxy对象,

用方法名字和返回值生成2个方法签名对象赋值给MethodProxy对象,

createInfo存放代理类和被代理类的信息,MethodProxy只关心test方法,其他方法不关心的。

MethodProxy 对象具体关心的是被代理对象的单个方法。当你创建一个CGLIB代理时,对于被代理对象的每个方法,CGLIB会生成一个相应的MethodProxy实例

在代理对象调用test()方法时候就会用到MethodProxy,就能用它的invoke方法和invokeSuper方法

传进来后用它的invoke和invokeSuper方法

invoke()方法实现

invokeSuper()方法

两个方法很像,区别一个是f1,i1

一个是f2,i2

就是对应的一个是invoke一个是invokeSuper

fastClassInfo这个属性一开始是空的

在init()方法赋值

Init()方法详解

用了MethodProxy对象后就会多2个类出来,这2个类都会继承FastClass类

都是FastClass的代理类

  1. 被代理类的FastClass:这个FastClass是针对原始被代理类生成的。它包含了被代理类所有方法的索引,并能够快速地调用这些方法。
  2. 代理类的FastClass:CGLIB还会为代理类自身生成一个FastClass。由于代理类重写了被代理类的所有方法,在这些重写的方法中通常会调用MethodInterceptor,代理类的FastClass同样包含了这些重写方法的索引,并可以快速调用它们。

如果用反射的话,首先得生成test方法和CGLIB$test$4方法的Method对象再去通过反射调用,效率比较低。

那FastClass如何加速?

FastClass该类会针对UserService的各个方法去返回对应的下标。传入方法签名,拿到下标

helper方法会生成对应的类

然后调用getIndex方法拿到test方法和CGLIB$test$4方法签名的下标索引是什么

找到下标后就设置到fastClassInfo属性里去

然后调用MethodProxy对象的invoke方法时候,就会传入test()方法或者CGLIB$test$4()方法对应的下标。

然后直接调用的你传入对象的test()方法或者CGLIB$test$4()方法,所以会更快

但是invokeSuper方法在执行CGLIB$test$4()方法,也就是会执行你想要代理的那个对象的原本的test方法前会强制转换

强制转换成UserService的代理类【里面才有CGLIB$test$4()方法】

所以这里才会报错转换失败,因为target是你自己new的需要被代理的那个对象,不能把UserService转成UserService的代理类型 ,因为UserService是父类。你只能把UserService的代理类型 转成UserService。

MethodProxy总结

代理类生成的时候里面的静态代码块构造出来的MethodProxy对象,当你用MethodProxy对象调用invoke或者invokeSuper方法的时候,会传入代理对象o或者被代理对象【自己new出来的需要被代理的那个类 比如UserService】和参数,然后调用init()方法,里面的helper方法会生成

2个FastClass的代理类【继承了FastClass的两个类 代理类的FastClass和被代理类的FastClass】,然后传入方法签名【静态代码块的create方法里就赋值好的方法前面 比如test方法】拿到索引下标。然后调用MethodProxy对象的invoke方法时候,就会传入test()方法或者CGLIB$test$4()方法对应的下标,就会去执行下标对应的方法,如果是执行invokeSuper还会强制转换成xxx的代理类型,如果你传的是xxx【父类】强转成子类肯定报错。底层不是用的反射,所以更快叫FastClass

JDK动态代理源码分析

除开cglib技术,jdk本身也提供了一种创建代理对象的动态代理机制,但是它只能代理接口,也就是UserService得先有一个接口才能利用jdk动态代理机制来生成一个代理对象,比如:

public interface UserInterface {
	public void test();
}

public class UserService implements UserInterface {

	public void test() {
		System.out.println("test...");
	}

}

利用JDK动态代理来生成一个代理对象:

UserService target = new UserService();

// UserInterface接口的代理对象   传入类加载器   你要代理的接口 new一个InvocationHandler 里面写你要增强的方法
Object proxy = Proxy.newProxyInstance(UserService.class.getClassLoader(), new Class[]{UserInterface.class}, new InvocationHandler() {
	@Override
    // proxy代理对象,method当前执行的方法,args参数 
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("before...");
		Object result = method.invoke(target, args);
		System.out.println("after...");
		return result;
	}
});

UserInterface userService = (UserInterface) proxy;
userService.test();

如果你把new Class[]{UserInterface.class},替换成new Class[]{UserService.class},允许代码会直接报错:

Exception in thread "main" java.lang.IllegalArgumentException: com.zhouyu.service.UserService is not an interface

表示一定要是个接口。

由于这个限制,所以产生的代理对象的类型是UserInterface,而不是UserService,这是需要注意的。

如何根据接口构造代理对象的?

生成代理类cl

拿到代理类的构造器和Handler对象,传递给cons的构造方法。

把你自己写的Handler传给代理类的构造方法去生成代理对象

代理类如何产生?

从缓存里拿的

key就是要代理的接口

value就是去生成代理类,作为value存起来

key就是ClassLoader,Parameter就是接口

这个key就是最终缓存的key,根据接口生产的subKey

根据接口个数返回key名字

每个ClassLoader有个单独的小的map去缓存某个我这个ClassLoader里面 某个接口所对应的代理类

传入ClassLoader,接口,key,缓存的map都传进去

根据ClassLoader和接口传给这个方法生成代理类最后装进缓存map中。

apply()生产代理类

遍历每个接口,拿到方法,然后包信息啊各种信息拿到后

去生成具体的class文件

会继承Proxy类实现你传进去的那个接口

然后调用代理类的构造方法,传进你写的Handler那个类

调用的是super()也就是JDK本身提供的类,赋值给h属性

然后调用h属性的invoke方法,也就是自己写的那个Handler的invoke方法,把代理对象自己传进去,m3就是接口的test方法的Method对象,test方法没有参数,所以传的null。

JDK动态代理源码总结

ASM技术

可以直接操作字节码,用的字节码指令

javasist这个工具比ASM更好用【Dubbo】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/474880.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PTA-练习4

目录 实验7-1-1 简化的插入排序 实验7-1-5 交换最小值和最大值 实验7-1-6 求一批整数中出现最多的个位数字 实验7-1-8 找出不是两个数组共有的元素 实验7-1-9 求整数序列中出现次数最多的数 实验7-1-10 组个最小数 实验7-1-11 装箱问题 实验7-1-1 简化的插入排序 //先将…

明明是字符串,为什么写的是char *str

字符串匹配 /* 暴力 将目标串s的第一个字符和模式串T的第一个字符进行匹配 相等&#xff0c;则比较s的第二个和T的第三个 不想等&#xff0c;比较s的第二个和T的第一个 如此循环 O(m*n)*/ #include<bits/stdc.h> using namespace std;int BF(char *str,char *sub) { //s…

聊聊SAP内存和ABAP内存

1、区别与联系 SAP内存&#xff08;SAP Memory&#xff09;和ABAP内存&#xff08;ABAP Memory&#xff09;&#xff0c;两者都可用于编程中的数据传递&#xff0c;将数据通过ID进行绑定&#xff0c;之后在其他程序中通过ID获取。 1.1、语法区别 1、SAP内存通过SET/GET PARA…

node.js实战 笔记 (补补前端Orz

Node.js是什么 Node.js是一个基于Chrome V8引擎的javascript运行环境。 Node.js使用一个事件驱动的非阻塞式IO模型。 Node.js和浏览器的区别 Node.js可以操作电脑&#xff0c;浏览器的javascript环境只能操作浏览器 Node.js多了一些内置模块&#xff0c;以及环境变量&#x…

【Redis】聊聊Redis常见数据类型底层结构

对于Redis来说&#xff0c;其实我们操作的数据类型就是String,List、Set、Zset、Hash&#xff0c;但是具体低层使用什么编码进行存储&#xff0c;在面试中是一个高频面试题&#xff0c;所以今天大概整理下对应的。 RedisObject与DictEntry 其实对于key来说一般都是String类型…

基于springboot+vue的在线互动学习网站

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

下载并安装ChatGPT Chrome扩展插件

解压 打开chrome浏览器并下面的步骤操作 参考链接&#xff1a; http://doc.xutongbao.top/docs/ai/994

华为配置Wi-Fi终端定位实验

配置Wi-Fi终端定位示例 组网图形 图1 配置Wi-Fi终端定位示例组网图 业务需求组网需求数据规划配置思路配置注意事项操作步骤配置文件 业务需求 管理员希望在已有WLAN覆盖业务的基础上&#xff0c;还可以定位覆盖区域内的终端位置信息。使用Wi-Fi终端定位方案&#xff0c;无需新…

C语言基础(十六)通过指针来输入和获取结构体的变量值

老样子&#xff0c;先看代码 #include <stdio.h> #include <string.h>#define NLEN 30 struct namect{char fname[NLEN];char lname[NLEN];int letters; };void getinfo(struct namect *); void makeinfo(struct namect *ptr); void showinfo(const struct namec…

如何在 Postman 中执行断言测试?

在当今的软件构建流程中&#xff0c;应用程序编程接口&#xff0c;简称 API&#xff0c;起到了不可或缺的作用&#xff0c;它们使得不同的软件应用能够互相沟通和交换数据。随着应用程序的不断演进变得越发复杂&#xff0c;保障API的可靠性及其稳定性显得格外关键。正因如此&am…

使用appuploder流程

使用appuploder流程笔记 1.如何没有账号去apple官网注册一个&#xff0c;地址&#xff1a;https://developer.apple.com/account 2.下载解压appuploder&#xff0c;双击打开&#xff0c;用刚刚注册的账号登录&#xff0c;下载地址&#xff1a;http://www.applicationloader.n…

员工上班摸鱼怎么管理

在当今快节奏的工作环境中&#xff0c;员工上班摸鱼的现象屡见不鲜。 这种行为不仅影响了工作效率&#xff0c;还可能对团队氛围和企业文化造成负面影响。 员工上班摸鱼的原因&#xff1f; 有些员工可能是因为工作压力过大&#xff0c;需要短暂的休息和放松&#xff1b; 有些…

人脸表情识别系统项目完整实现详解——(二)使用SSD模型检测人脸

摘要&#xff1a;人脸检测是人脸表情识别系统中至关重要的一环&#xff0c;其准确性直接影响到整个系统的性能表现。本文介绍了使用SSD模型和OpenCV进行高效人脸检测的完整代码实现。我们详细介绍了SSD人脸检测器的工作原理&#xff0c;包括如何加载预训练的SSD模型&#xff0c…

C语言经典算法-5

文章目录 其他经典例题跳转链接26.约瑟夫问题&#xff08;Josephus Problem&#xff09;27.排列组合28.格雷码&#xff08;Gray Code&#xff09;29.产生可能的集合30.m元素集合的n个元素子集 其他经典例题跳转链接 C语言经典算法-1 1.汉若塔 2. 费式数列 3. 巴斯卡三角形 4. …

【Spring IOC/DI】bean 的 5 种注册 与 5 种注入

什么是 bean 一个 bean 就是一个实例化对象 User user new User() 上面这行代码中的 user&#xff0c; 就是 User 类的实例化对象&#xff0c;即一个 bean&#xff08;User Bean&#xff09; 什么是 IOC Inversion of Control 控制反转&#xff08;反转对 bean 的控制&#…

ElasticSearch之Ingest Pipeline和Painless Script

写在前面 如果是我们需要在写入文档或者是返回文档时&#xff0c;进行修改字段值&#xff0c;或者增加字段等操作时&#xff0c;就可以考虑使用ingest pipeline和painless script。如下的需求&#xff1a; 1:ingest pipeline 在es 5中引入了一种新的节点类型ingest node&am…

安科瑞智能断路器产品介绍【可监可控 远程操控 短路保护】

开发背景 过去几年智慧用电的产品应用中&#xff0c;大多数只安装于进线测。主要存在以下几个问题&#xff1a;难定位&#xff0c;不知道具体哪个回路出线问题&#xff0c;排查困难&#xff1b;出线过载或线缆温度过高无法知晓&#xff1b;即使是出线回路安装了的场景&#xf…

个人开发App成功上架手机应用市场的关键步骤

目录 1. 苹果审核和APP备案 2. APP上架操作步骤 3. 审核和发布 4. 上线工作 总结 参考资料 在当前移动应用市场竞争激烈的背景下&#xff0c;个人开发App如何成功上架成为开发者们必须面对的重要任务。本文将重点介绍自建App上架至手机应用市场的流程&#xff0c;包括苹果…

2024你必须知道的外贸形势!

2024年外贸形势下的新机会在哪里&#xff1f;今天Erica给大家总结了几个主要市场的形式。 喜欢的话点点关注吧~ 欧美市场2024年应谨慎开发 海关总署11月7日发布的数据显示&#xff0c;前10个月&#xff0c;今年中国对欧洲出口呈下降趋势&#xff0c;中国与欧盟贸易总值为4.5…

前端项目,个人笔记(六)【无限滚动 + 拦截器】

目录 1、无限滚动 2、使用pinia进行用户数据持久化 3、完善个人笔记三中的拦截器 请求拦截器&#xff1a; 响应拦截器&#xff1a; 1、无限滚动 使用elementplus中提供的&#xff1a; 代码&#xff1a; <div class"body" v-infinite-scroll"load"…