详解Spring中的Aop编程原理JDK动态代理和CGLIB动态代理

😉😉 学习交流群:

✅✅1:这是孙哥suns给大家的福利!

✨✨2:我们免费分享Netty、Dubbo、k8s、Mybatis、Spring...应用和源码级别的视频资料

🥭🥭3:QQ群:583783824   📚📚  工作微信:BigTreeJava 拉你进微信群,免费领取!

🍎🍎4:本文章内容出自上述:Spring应用课程!💞💞

💞💞5:以上内容,进群免费领取呦~ 💞💞💞💞

一:AOP编程

Aop编程全称为:Aspect Oriented Programming面向切面编程,什么叫做面向切面编程?oop面向对象编程,pop面向过程编程。

1:面向对象编程含义

以对象为基本单位的程序开发,通过对象间的彼此协同,相互调用来达到开发目的。

2:面向过程编程

        以过程为基本单位的层程序开发,以过程之间的相互协同,相互调用来达到开发的目的,这里的古城值得就是方法或者说函数

3:面向切面编程

        以切面为基本单位的层程序开发,以切面之间的相互协同,相互调用来达到开发的目的。如果我们能找到切面,并且以切面进行开发,就是以切面进行开发

        切面:切入点+额外功能。他们两个被组装后形成切面。这是spring动态代理的开发当中是面向切面编程的基础。spring的动态代理开发其实就是面向切面编程,这两个是完全等价的,Aop编程的背后的技术就是动态代理技术。

4: 什么是Aop编程

        Aop编程的本质就是Spring动态代理的开发,通过代理为原始类增加额外的功能,好处就是利于原始类的维护,有了Aop编程之后就可以取代oop这个说法是不正确的,她是从oop上发展来的,不存在取代的关系,他仅仅是oop编程的一种有益的补充。

5:Aop的开发步骤

        准备原始对象—准备原始功能-切入点—组装成切面(额外功能+切入点)
        这里的开发步骤和spring的动态代理技术是没有任何区别的。
        切面:切入点+额外功能
        为什么spring的切入点+额外功能叫做切面呢?

        形成了一个切面,所以叫切面。

二:AOP底层实现原理

        AOP底层实现原理也就是动态代理的底层实现原理,搞明白这个事需要知道几个核心问题

1:核心问题提出

         Aop如何帮我们创建这个动态代理类,这个动态代理类是依赖于一门动态字节码技术,动态字节码技术到底是如何通过我们的编码将JVM中的动态代理类创建出来的呢?

        Spring在做动态代理或者Aop编程的时候,有一个非常有意思的事,他是怎么把原始对象的id值,拿到的是动态代理对象的id值的呢?

        以上的问题就等效与:Spring是如何创建和加工动态代理对象的

2:如何创建动态代理类

        Spring创建动态代理类有两种方式,第一种方式是jdk动态代理,第二种是Cglib动态代理

1:JDK动态代理

        这里研究的是动态代理对象的创建,而并不是动态代理类的创建,真正的动态代理类的创建是Proxy.newInstance(....)这个方法执行的时候,在jvm内存中创建的动态代理类,上边这个方法获取的是动态代理对象。

        Spring的动态代理或者说Spring的Aop的代理对象的创建和使用,他的本质也是使用的是JDK动态代理或者cglib动态代理,他的写法已经在是Spring当中固定好了,所以,原始对象必须得是交由Spring进行创建,另外本质的Spring向外暴露的动态代理的实现方式还是对JDK底层动态代理实现或者cglib动态代理实现的封装。

        创建动态代理的三要素:原始对象、额外功能、动态代理和原始类实现相同的接口。

        这个动态代理在这一组参数当中,invocationHandler这是一个接口,这个接口对应了我们的额外功能,在这个接口当中有一个invoke方法,实现了这个接口之后呢,这个接口当中有三个方法,方法中有三个参数,如下,执行任何一个方法(原始方法)都需要三个要素,对象、方法、参数列表,不论是普通调用,或者使用反射,都是这么个情况,所以第二个参数是方法对象,第三个参数是方法的参数数组,还需要第一步创建的原始对象,这样就可以执行原始方法了,这个invoke方法的返回值代表的是原始方法的返回值,这里的这个接口当中的invoke方法是原生jdk提供的动态代理的实现方式,spring对我们暴露的实现方式呢有那个MethodInceptor这个接口,这个接口当中的invoke方法中有一个参数对象,可以执行proceed()方法其实这个就是对原生jdk的一个封装,原生的肯定复杂一点,封装之后的肯定更好用。

        代理对象要和原始对象实现相同的接口,为什么我们动态代理对象要和原始对象实现相同的接口呢,因为都耦合与一个接口才能迷惑调用者呀

        我们创建的是jdk的动态代理,也需要实现和目标类相同的接口,这个条件不是我们来实现,而是proxy.newProxyInstance这个方法类帮我们实现,这里的传入的第二个参数就是干这个用的。提供这个参数是使用,是采用UserService.class对象,就可以使用userService.getClass().getInterfaces();这个形式获取就可以了。

        //创建原始对象UserService userService = new UserServiceImpl();//创建动态代理基于jdk,创建动态代理基于jdk的方式,需要调用jdk为我们提供的一个工具proxy//这个方法的返回值就是这个类的动态代理对象。//这个方法需要三个参数,第一个参数


        到这里我们实现动态代理的的三个要素就准备好了,原始对象我们自己创建的

        额外功能由于我们采用的事JDK形式的动态代理,所以我们使用的接口。将额外功能书写在这接口实现类中的invoke方法当中,进行响应的处理,实现同样的接口,就是获取原始对象实现的接口传递给这个方法,然而在这个Proxy.newProxyInstatnce()这个方法当中还有一个来加载器对象这样的一个参数,这样的一个参数究竟作用是什么呢,这就是
        类加载器的作用:(单纯谈类加载器的作用)
        1:通过类加载器把对应类的字节码文件加载到虚拟机当中,
        2:通过类加载器创建类的class对象,进而创建这个类的实例。
        Class对象记录着这个类最完整的信息。
        我们书写的java文件是java的源码文件,java文件经过编译之后的文件称为字节码文件,也就是.class文件,程序的运行是class文件加载到jvm内存当中之后被执行的结果,那么将class字节码文件加载到虚拟机当中是谁干的呢? 是类加载器,字节码进入到虚拟机当中之后,类加载器帮我们加载class文件到jvm内存之后,还会帮助我们创建这个类的Class对象,这里边有这个类最全的信息,进而可以创建这个类的实例。这个类加载器在我们的创建对象的过程当中是尤为重要的,类加载器这么重要,如何获取类加载器呢?
        虚拟机会为每一个class分配对应的类加载器,有了这个类加载器之后呢,我们就可以创建这个类的字节码对象了,在动态代理的创建中为什么需要类加载器呢?动态代理也是在虚拟机当中获得动态代理类,进而帮我们创建动态代理对象,这个动态代理类没有源文件,也买有字节码文件,动态代理类他是怎么获取这个类的字节码来创建对象的呢?动态代理是通过动态字节码技术来创建字节码,直接就将字节码写到了虚拟机当中,没有一个加载字节码的过程,这个动态字节码技术在我们的虚拟机当中是怎么实现的?我们是怎么对他进行编程的呢?他暴露给我们程序员的就是Proxy.newProxyInstance()这个方法,这个方法中就会使用这个动态字节技术,作为这个动态字节码技术,而且我们在这个方法中已经传入了所有的创建动态代理类所需要的原材料,那么使用这个动态字节码技术呢,直接将字节码写到了虚拟机当中,所以呢类加载的第一个作用就没有用武之地了,这里的类加载器起的作用个呢是床架你这个动态代理类的Class对象,也就是发挥他的第二个技术,省略了一个加载的过程,有了这个字节码对象我们就能创建这个类的实例了(所有的创建对象都是如此),到了这里呢,就不太好搞了,之前的普通类加载的时候呢是虚拟机根据字节码文件(.class文件)进行分配,现在呢,动态代理类是动态字节码,并没有这个Class文件,没有这个class文件,虚拟机就不会为他分配类加载器,也就没有办法生成Class对象,此时在动态代理创建的过程当中,需要类加载器创建代理类的Class对象,可是动态代理类没哟对应的Class问文件,虚拟机也就不会为他分配类加载器,但是我们又需要,怎么办?借一个,借用的是源文件的类加载对象,来过来为动态代理类创建Class对象的,进而创建这个类的实例,这也是这里为什么需要一个类加载器参数的原因。借用谁的?比方说UserService,或者UserServiceImpl

package com.spring;import com.spring.proxy.User;
import com.spring.proxy.UserService;
import com.spring.proxy.UserServiceImpl;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** @Auther: DaShu* @Date: 2021/6/23 19:23* @Description:*/
public class JdkProxyTest {public static void main(String[] args) {//创建原始对象UserService userService = new UserServiceImpl();//创建动态代理基于jdk,创建动态代理基于jdk的方式,需要调用jdk为我们提供的一个工具proxy//这个方法的返回值就是这个类的动态代理对象。//这个方法需要三个参数,第一个参数//为了简单采用内部类的方式来实现invocationhANDLERInvocationHandler invocationHandler = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("-----------这里是代理对象中的额外功能-------------");//目标方法执行Object invoke = method.invoke(userService, args);System.out.println("-----------这里是代理对象中的额外功能-------------");return invoke;}};UserService proxyUserService = (UserService) Proxy.newProxyInstance(JdkProxyTest.class.getClassLoader(),userService.getClass().getInterfaces(),invocationHandler);proxyUserService.login("崔磊磊", "12345");proxyUserService.register(new User());//-----------这里是代理对象中的额外功能-------------//UserServiceImpl.login//-----------这里是代理对象中的额外功能-------------//-----------这里是代理对象中的额外功能-------------//UserServiceImpl.register//-----------这里是代理对象中的额外功能-------------}
}


        我们说这个了类的类加载器是借的,我们借的谁的都行。

        注意:jdk8.0之前呢 内部类访问外部类类的变量需要外部类的成员边量是final修饰的,jdk8.0之后呢会自动加上这个final,

2:CGLIB动态代理

        cglib动态代理也是spring底层默认支持的一种动态代理创建方式,他跟jdk动态代理的区别在哪里呢?
        jdk动态代理中:原始对象需要实现一个接口,比方说UserServiceImpl 实现 UserService接口,代理对象需要和原始兑现实现相同的接口,为什么会有一个这样的要求呢?第一保证代理类和原始类方法一致,第二点保证代理类当中提供对应方法的新的实现,就可以加入额外功能了,也可以调用原始方法,所以说我们的代理类,这样代理的诉求就实现了。这就是jdk的动态代理。
作为cglib来讲创建动态代理类有什么特点呢?原始类没有实现任何接口,没有实现任何接口的这样的代理类创建代理对象的时候,反正我们的初衷就是我们的代理类和原始类中必须有相同的方法,新的实现里边包括这个额外功能和原始功能的调用,新的代理类可以使用什么来开发呢?cglib要求他所创建的这个代理类要去继承这个原始类,这样来保证你有的这个方法我也有,变相也保证了代理类和原始类有这个共同方法的要求,然后就有机会添加新的实现,调用原始方法super.就可以了,这样的实现的方式不同,但是最终的结果是一致的。cglib采用的事这个父子继承的关系,不管原始类有没有实现接口。


        cgilib实现动态代理的的步骤,1:准备原始对象UserService原始对象的userService不需要实现任何的接口,里边准备两个原始方法,第二部,创建代理对象,创建代理对象需要cgilib对应的jar包,这个jar包spring已经默认帮我们引入进来了,这样我们就可以直接使用,cgilib的jar包中最为关键的类是一个Enhancer的接口,作为这个类来讲,我们调用create方法进行创建代理对象,这里边需要几个参数,他所需要的参数和jdk的参数是一模一样的,只不过这里边的接口类型指定的值父类的class对象,还有一个父类的原始功能,Enhancer.setClassLoader()Enhance.setSupperClass,Enhance.setCallBack(MethodInterceptor)这个接口是cgilib中的接口跟当时的spring不是一个接口,
创建这个cglib的Methodinterceptor的接口的时候,采用一个内部类的形式,创建他的子类的实例对象,子类对象需要重写他的intereceptor方法,
 

package com.spring.cglib;import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** @Auther: DaShu* @Date: 2021/6/24 19:41* @Description:*/
public class TestCglib {public static void main(String[] args) {UserService userService = new UserService();//通过cgilib方式进行创建,Enhancer enhancer = new Enhancer();enhancer.setClassLoader(TestCglib.class.getClassLoader());enhancer.setSuperclass(userService.getClass());MethodInterceptor interceptor = new MethodInterceptor() {@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("----------cgilib日志额外功能。----------");//等同于invocationHandler中的invoke方法,Object invoke = method.invoke(userService, args);return invoke;}};enhancer.setCallback(interceptor);UserService proxyUserService = (UserService)enhancer.create();proxyUserService.login();proxyUserService.register();//----------cgilib日志额外功能。----------//UserService.login//----------cgilib日志额外功能。----------//UserService.register}
}///**
// * @Auther: DaShu
// * @Date: 2021/6/24 19:39
// * @Description: 这个代表原始类。
// */
//public class UserService {
//    public void login(){
//        System.out.println("UserService.login");
//    }
//    public void register(){
//        System.out.println("UserService.register");
//    }
//}

       JDK动态代理,Proxy.newProxyInstance()通过接口原始对象的代理类,cgilib动态代理依赖于Eahancer这个类,通过继承原始类创建原始类的代理类,在spring的后续过程中,如果在面试过程中可以把这些方法中的对应的参数说清楚,这是一个大大的加分项。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

yolov8模型 onnxruntime推理及可视化

参考:https://github.com/ultralytics/ultralytics/blob/main/examples/YOLOv8-ONNXRuntime/main.py 1、yolov8 onnxruntime推理代码 1)导出参考:https://blog.csdn.net/weixin_42357472/article/details/131412851 2)查看保存的模型onnx的输入格式等信息 登录https://n…

使用PCReg.PyTorch项目训练自己的数据集进行点云配准

项目地址: https://github.com/zhulf0804/PCReg.PyTorch/tree/main 网络简介: 网络是基于PointNet Concat FC的,它没有其它复杂的结构,易于复现。因其简洁性,这里暂且把其称作点云配准的Benchmark。因作者源码中复杂…

剑指 Offer(第2版)面试题 14:剪绳子

剑指 Offer(第2版)面试题 14:剪绳子 剑指 Offer(第2版)面试题 14:剪绳子解法1:动态规划解法2:数学 剑指 Offer(第2版)面试题 14:剪绳子 题目来源…

DOM 事件的注册和移除

前端面试大全DOM 事件的注册和移除 🌟经典真题 🌟DOM 注册事件 HTML 元素中注册事件 DOM0 级方式注册事件 DOM2 级方式注册事件 🌟DOM 移除事件 🌟真题解答 🌟总结 🌟经典真题 总结一下 DOM 中如何…

TCP连接为什么是三次握手,而不是两次和四次

答案 阻止重复的历史连接同步初始序列号避免资源浪费 原因 阻止重复的历史连接(首要原因) 考虑这样一种情况: 客户端现在要给服务端建立连接,向服务端发送了一个SYN报文段(第一次握手),以表示请…

固定Microsoft Edge浏览器的位置设置,避免自动回调至中国

问题描述 在使用Copilot等功能时,需要将Microsoft Edge浏览器的位置设置为国外。但每次重新打开浏览器后,位置设置又自动回调至中国,导致每次均需要手动调整。 原因分析 这个问题的出现是因为每次启动Microsoft Edge时,默认打开…

cmake和vscode 下的cmake的使用详解(三)

第七讲:【实战】使用 VSCode 进行完整项目开发 案例:士兵突击 需求: 1. 士兵 许三多 有一把 枪 ,叫做 AK47 2. 士兵 可以 开火 3. 士兵 可以 给枪装填子弹 4. 枪 能够 发射 子弹 5. 枪 能够 装填子弹 ——…

2022年9月6日 Go生态洞察:Go的漏洞管理新支持

🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文…

docker-速通

1.命令-镜像操作 docker pull nginx #下载最新版 docker pull nginx:1.20.1 #下载指定版本 镜像名:版本名(标签) docker images #查看所有镜像 # 如果只写镜像名实际就是redis redis:latest 记住这个不是命令 docker rmi 镜像名:版本号/镜像id…

利用段落检索和生成模型进行开放域问答12.2

利用段落检索和生成模型进行开放域问答 摘要引言2 相关工作3 方法 摘要 事实证明,开放域问答的生成模型具有竞争力,无需借助外部知识。虽然很有希望,但这种方法需要使用具有数十亿个参数的模型,而这些模型的训练和查询成本很高。…

在linux服上部署vue+springboot+nginx项目

一、环境准备 1、安装winscp便于可视化操作linux:winscp安装及关联putty使用_putty.exe没有找到_cherishSpring的博客-CSDN博客 2、安装jdk:linux系统安装jdk-CSDN博客 3、安装mysql:Linux7安装mysql数据库以及navicat远程连接mysql-CSDN博…

Fiddler抓包工具之fiddler设置断点和简单的并发测试

断点有两种方式: 1、全局断点 2、局部断点 全局断点 全局断点的特点是:不能针对一个请求,是给所有抓到的请求打断点 全局断点如何设置: 1、快速设置断点:直接点击底部状态栏断点处 ;点击第一下是请求…

【算法专题】二分查找

二分查找 二分查找1. 二分查找2. 在排序数组中查找元素的第一和最后一个位置3. 搜索插入位置4. x 的平方根5. 山脉数组的峰顶索引6. 寻找峰值7. 寻找旋转排序数组中的最小值8. 点名 二分查找 1. 二分查找 题目链接 -> Leetcode -704.二分查找 Leetcode -704.二分查找 题…

【Geoserver】SLD点位样式(PointSymbolizer)设计全通

SLD文件可以控制geoserver的样式管理,这里专门针对点位进行设计,首先点位的设计需要用到这面这个大标签 之前的项目中已经用到了很多关于面的样式管理,这里新学习的是关于点的样式管理 PointSymbolizer 参考资料地址:https://doc…

LeetCode算法题解(动态规划)|LeetCode1143. 最长公共子序列、LeetCode1035. 不相交的线、LeetCode53. 最大子数组和

一、LeetCode1143. 最长公共子序列 题目链接:1143. 最长公共子序列 题目描述: 给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。 一个字符串的 子序列 是指这样一…

论文编写软件latex安装教程

目录 1.下载安装包2.安装texlive 本人系统为windows,本教程基于windows系统,如果是其它系统请参考对应教程,注意选择对应系统的安装包! 1.下载安装包 有三种集成环境安装包 texlive 是主流的环境,集成了较多的包&…

【数据结构】二叉树---C语言版

二叉树 一、树的概念及结构1.树的概念2.树的相关概念3.树的表示4.树在实际中的应用 二、二叉树的概念及结构1.二叉树的概念2.满二叉树3.完全二叉树4.二叉树的性质5.二叉树的储存结构 三、二叉树的遍历1.前序遍历2.中序遍历3.后序遍历4.层序遍历 四、手撕二叉树(务必…

MySQL 临时数据空间不足导致SQL被killed 的问题与扩展

开头还是介绍一下群,如果感兴趣PolarDB ,MongoDB ,MySQL ,PostgreSQL ,Redis, Oceanbase, Sql Server等有问题,有需求都可以加群群内,可以解决你的问题。加群请联系 liuaustin3 ,(共1730人左右 1 2 3 4 5&#xff0…

Mover Creator--功能简介

Mover Creator是一款AFSIM软件工具,提供方便易用的基于GUI的应用程序,帮助用户创建用于空中运动器的AFSIM输入文件,包括WSF_P6DOF_MOVER和WSF_GUIDED_MOVER。使用自定义定义的基于图形的模型定义,用户可以对飞机、武器和发动机进行…

邮政快递查询,邮政快递单号查询,用表格导出查询好的物流信息

批量查询邮政快递单号的物流信息,并以表格的形式导出查询好的物流信息。 所需工具: 一个【快递批量查询高手】软件 邮政快递单号若干 操作步骤: 步骤1:运行【快递批量查询高手】软件,第一次使用的伙伴记得先注册&am…