【Spring】Spring AOP底层原理:JDK动态代理和CGLIB动态代理

news2024/11/14 14:21:28

目录

1、代理模式

1.1、静态代理

1.2、动态代理 

2、JDK 动态代理

2.1、jdk动态代理简介

2.2、使用JDK动态代理机制步骤

3、CGLIB 动态代理

3.1、CGLIB 动态代理的特性

3.2、CGLIB的核心类

3.3、CGLIB 动态代理步骤

4、JDK 和 CGLIB 创建代理对象的区别

​编辑

1、代理模式

        代理模式(Proxy Pattern)是一种结构性模式。代理模式为一个对象提供了一个替身,以控制对这个对象的访问。即通过代理对象访问目标目标对象,可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。

        代理模式主要有三种形式,分别是静态代理、动态代理(也称JDK代理、接口代理)和cglib代理(在内存动态创建对象而不需要实现接口,也可属于动态代理得范畴)

1.1、静态代理

 静态代理在前一章中已经说过了:【Spring】Spring AOP底层原理前置知识:代理模式中的静态代理-CSDN博客

1.2、动态代理 

        Spring AOP 的实现原理是基于动态代理和字节码操作的。而动态代理是基于反射机制的。

        在编译时, Spring 会使用 AspectJ 编译器将切面代码编译成字节码文件。在运行时, Spring 会使用 Java 动态代理或 CGLIB 代理生成代理类,这些代理类会在目标对象方法执行前后插入切面代码,从而实现AOP的功能。

        相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类(CGLIB 动态代理机制)。从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到JVM 中的。说到动态代理,Spring AOP、RPC框架是两个不得不的提的,它们的实现都依赖了动态代理。动态代理在日常开发中使用的相对较小,但是在框架中的几乎是必用的一门技术。学会了动态代理之后对于理解和学习各种框架的原理也非常有帮助。

Spring AOP 可以使用两种代理方式:JDK动态代理和 CGLIB 代理。如果目标对象实现了至少一个接口,则使用JDK动态代理;否则,使用 CGLIB 代理。下面分别介绍这两种代理方式的实现原理。

2、JDK 动态代理

2.1、jdk动态代理简介

jdk动态代理:使用java反射包中的类和接口实现动态代理的功能。

反射包 java.lang.reflect , 里面有三个类 : InvocationHandler , Method, Proxy

在 Java 动态代理机制中InvocationHandler 接囗和 Proxy 类是核心。

Proxy 类中使用频率最高的方法是: newProxyInstance(),Proxy,这个方法主要用来生成一个代理对象,。

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
throws IllegalArgumentException
{
......
}

这个方法一共有 3 个参数:

(1)loader:类加载器,用于加载代理对象,传入被代理类的类加载器

(2)interfaces:被代理类实现的一些接口;被代理的类必须要实现这个接口

(3)h:实现了 InvocationHandler 接囗的对象,我们自己写的,代理类要完成的功能。

要实现动态代理的话,还必须需要实现 InvocationHandler 来自定义处理逻辑。当我们的动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现了 InvocationHandler 接口的实现类中重写了的 invoke 方法来调用。

示例:

还没使用JDK动态代理机制时,创建的对象就是被代理类自己

//被代理类要实现的接口IUserService
public interface IUserService {
    void add();
}


//被代理类UserService
public class UserService implements IUserService{
    @Override
    public void add(){
        System.out.println("增加用户");
    }
}


//测试类TestJDKProxy
public class TestJDKProxy {

    @Test
    public void test(){
        IUserService userService = new UserService();
        System.out.println(userService.getClass());
    }

}

这时候还没有使用动态代理来获取代理类,所以这里IUserService userService = new UserService();创建的对象不是动态生成的代理对象

2.2、使用JDK动态代理机制步骤

(1)创建接口,定义目标类要完成的功能

(2)创建目标类实现接口

(3)创建InvocationHandler接口的实现类,在invoke方法中完成代理类的功能
        1.调用目标方法
        2.增强功能

(4)使用Proxy类的静态方法,创建代理对象。 并把返回值转为接口类型。

第一步和第二步我们前面还没使用JDK动态代理机制时已经完成了,现在只需要完成第三步和第四步

第三步:创建InvocationHandler接口的实现类MyHandler

public class MyHandler implements InvocationHandler {

    Object target; //target代表被代理类的实例对象

    //通过这个方法可以把被代理类的实例对象,也就是目标对象传递进来赋值给target,
    //后面调用被代理类里面的方法要用到
    public MyHandler(Object target){
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //实现增强代码的地方,这里就用一个简单的打印语句代替一下了
        System.out.println("增强的代码:前置通知");

        //执行目标方法,这里是基于反射机制来实现的
        Object returnValue = method.invoke(target, args);

        System.out.println("增强的代码:后置通知");

        //返回目标方法的返回值
        return returnValue;
    }
}

第四步:使用Proxy类的静态方法,创建代理对象

//测试类TestJDKProxy
public class TestJDKProxy {

    @Test
    public void test(){
        IUserService userService = (IUserService) Proxy.newProxyInstance(
            UserService.class.getClassLoader(),//目标对象(UserService)的类加载器,负责向内存中加载对象的,这里使用反射机制来获取
            UserService.class.getInterfaces(),//接口,目标对象(UserService)实现的接口,也是反射获取的
            new MyHandler(new UserService())//自己写的实现了 InvocationHandler 接口的实现类,在里面写代理类要完成的功能,
            // 我只在里面简单写了打印语句打印“前置通知”和“后置通知”
        );
        //生成出来的代理实现了IUserService接口,这就是为什么上面的方法一定要传入目标对象(UserService)实现的接口IUserService
        System.out.println(userService.getClass()); //打印看看现在拿到的对象是不是动态创建的代理对象
        userService.add(); //执行这个代理对象的方法,他会执行代理类中的增强方法和被代理类中的add方法
    }

}

运行结果:

        所以在Spring AOP底层原理中,Spring AOP就是帮我们做了上面的第三步和第四步,以此来实现JDK动态代理机制为我们创建代理类对象,然后把这个代理类对象放入到IOC容器当中。我们去IOC容器中拿被代理类(就相当于上面例子中的UserService类)的Bean时,实际上拿的是他的代理类(相当于上面例子中的class jdk.proxy2.$Proxy8)的Bean,这就是为什么可以把增强的代码以切面编程的方式加到目标对象中去运行的原因

        总之,JDK 动态代理是 Java 自带的动态代理实现方式。使用JDK动态代理时,需要目标对象实现至少一个接口。JDK 动态代理会在运行时生成一个实现了目标对象接口的代理类,该代理类会在目标对象方法执行前后插入切面代码。

3、CGLIB 动态代理

3.1、CGLIB 动态代理的特性

        (1)cglib动态代理是针对类来实现代理的,它的原理是对指定的目标类(被代理类)生成一个子类,并覆盖其中方法实现增强。而JDK的动态代理机制只能代理实现了接口的类,而没有实现接口的类就不能实现JDK的动态代理。所以使用cglib实现动态代理,完全不受代理类必须实现接口的限制。

        (2)cglib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。

        (3)因为cglib动态代理采用的是继承,所以不能对final修饰的类进行代理。

        (4)由于cglib是一个第三方的框架,不是JDK自带的,所以要引入maven依赖。但是在的Spring3版本之后cglib就被整合进Spring中了,就不再需要引入cglib的maven依赖了

引入cglib的maven依赖

//引入cglib的maven依赖
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.6</version>
</dependency>

3.2、CGLIB的核心类

        (1)net.sf.cglib.proxy.Enhancer: Enhancer(代理工厂类,也可以叫做增强类)是CGLIB中的主要类之一,用于创建代理对象。通过Enhancer可以指定被代理类、回调方法和拦截器等信息,然后生成代理对象的字节码。

        (2)net.sf.cglib.proxy.MethodInterceptor: MethodInterceptor(拦截类)是CGLIB中的一个接口,用于实现代理对象的拦截逻辑。当代理对象的方法被调用时,MethodInterceptor中的intercept方法会被调用,开发者可以在该方法中添加额外的逻辑。类似于JDK动态代理中的InvocationHandler接口

        (3)net.sf.cglib.proxy.Callback: Callback是一个接口,它定义了代理对象的回调方法。MethodInterceptor就是继承了Callback接口的一个更成熟的接口,用于拦截方法调用。CGLIB还提供了其他类型的Callback,如NoOp、LazyLoader等,用于实现不同的代理逻辑。

MethodInterceptor继承了Callback接口在源码中如下图:

3.3、CGLIB 动态代理步骤

(1)引入 CGLIB 依赖(好像Spring3版本之后cglib就被整合进Spring中了,就不需要引入依赖了)

(2)定义一个被代理类(这个被代理类可以实现接口,也可以不实现接口)

(3)定义一个拦截类并实现接口 MethodInterceptor

(4)创建代理工厂类实例

(5)生成代理类对象,并通过代理对象调用方法

示例:

//定义一个没有实现任何接口的被代理类UserService
public class UserService{
    public void add(){
        System.out.println("增加用户");
    }
}


//定义一个拦截类MyMethodInterceptor并实现接口 MethodInterceptor
public class MyMethodInterceptor implements MethodInterceptor {

    Object target; //target代表被代理类的实例对象

    //通过这个方法可以把被代理类的实例对象,也就是目标对象传递进来赋值给target,
    //后面调用被代理类里面的方法要用到
    public MyMethodInterceptor(Object target){
        this.target = target;
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        //实现增强代码的地方,这里就用一个简单的打印语句代替一下了
        System.out.println("增强的代码:前置通知");

        //执行目标方法,这里是直接调用父类方法(就是直接调用被代理类的方法),相当于执行super.方法()
        Object returnValue = proxy.invoke(target, args);

        System.out.println("增强的代码:后置通知");

        //返回目标方法的返回值
        return returnValue;
    }
}


//创建代理工厂类的对象,然后通过代理对象调用方法
public class TestCGLIBProxy {

    @Test
    public void test(){
        //创建代理工厂类实例
        Enhancer enhancer = new Enhancer();

        //设置被代理的类
        enhancer.setSuperclass(UserService.class);

        //设置处理类
        enhancer.setCallback(new MyMethodInterceptor(new UserService()));

        //生成的代理类,这个代理类是继承自UserService的
        UserService userService = (UserService) enhancer.create();
        System.out.println(userService.getClass()); //打印结果为:class com.lt.autoProxy.cglib.UserService$$EnhancerByCGLIB$$beeda0dc
        userService.add(); //通过代理对象调用方法
    }

}

运行结果:

4、JDK 和 CGLIB 创建代理对象的区别

        (1)JDK 动态代理只能代理实现了接口的类,因为他生成出来的代理类是采用实现目标类的接口的方式,比如目标类UserService实现了接口lUserService,代理类的生成方式就是 public proxy$0 implements lUserService{} ,proxy$0就是JDK 动态代理生成出来的代理类(当然这只是一个例子,proxy$0是我为了好理解而取的代理类类名)

        (2)而 CGLIB 可以代理未实现任何接口的类。因为他生成出来的代理类是采用继承目标类的方式,比如目标类为UserService,代理类的生成方式就是 public $xxxxCqlib$ extends UserServcef} ,另外CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为final 类型的类和方法。

        (平常的业务是用不到动态代理的,一般都是框架的底层才会用到) 

补充:

        所以可以根据是否有实现接口来选择JDK动态代理或CGLIB,但是用Spring AOP来实现的话,无需关心,因为底层会自动根据被代理目标类是否实现接口自动选择JDK或CGLIB

        在Spring中默认用JDK动态代理 判断目标类是否实现接口,如果实现了接口就用JDK动态代理,否则就使用CGLIB动态代理

可以通过配置来改变Spring使用哪一种动态代理机制:

@EnableAspectAutoProxy(proxyTargetClass=false) = jdk动态代理 (默认) @EnableAspectJAutoProxy(proxyTargetClass=true)= cglib

在SpringBoot2.x版本后默认是使用CgLib作为默认的动态代理实现

@EnableAspectAutoProxy注解在SpringBoot的默认启动类上有一个,默认启动类上的这个注解设置的是proxyTargetClass=true,也就说默认是使用cglib动态代理的。如果想把他改成使用jdk动态代理,就可以在SpringBoot的配置文件application.properties里配置:spring.aop.proxy-target-class=false

推荐:

【Spring】Spring AOP底层原理前置知识:代理模式中的静态代理-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/m0_65277261/article/details/139106845?spm=1001.2014.3001.5501

【Spring】AOP中的核心概念:通知(Advice)和切点(Pointcut)_advice 切面-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/m0_65277261/article/details/138890487?spm=1001.2014.3001.5501

【Spring】初识 Spring AOP(面向切面编程)-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/m0_65277261/article/details/138724937?spm=1001.2014.3001.5501

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

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

相关文章

Linux网络编程:传输层协议|UDP

知识引入&#xff1a; 端口号&#xff1a; 当应用层获得一个传输过来的报文时&#xff0c;这时数据包需要知道&#xff0c;自己应该送往哪一个应用层的服务&#xff0c;这时就引入了“端口号”&#xff0c;通过区分同一台主机不同应用程序的端口号&#xff0c;来保证数据传输…

【高数】重点内容,公式+推导+例题,大学考试必看

目录 1 隐函数求导1.1 公式1.2 说明1.3 例题 2 无条件极值2.1 运用2.2 求解2.3 例题 3 条件极值3.1 运用3.2 求解3.3 例题 4 二重积分4.1 直角坐标下4.2 极坐标下4.3 例题 5 曲线积分5.1 第一型曲线积分5.2 第二型曲线积分5.3 例题 6 格林公式6.1 公式6.2 说明6.3 例题 &#x…

【强化学习】Q-learning,DQN,SARSA算法介绍

【强化学习】Q-learning&#xff0c;DQN&#xff0c;SARSA算法介绍 强化学习算法分类基于价值的方法基于策略的方法Actor-Critic方法 Q-learning算法DQN算法强化学习训练数据存在的问题经验回放机制备份网络机制 Sarsa算法总结 强化学习算法分类 按学习目标进行分类 可分为基于…

信息化赋能:干部监督工作的创新与实践

随着信息技术的迅猛发展&#xff0c;信息化手段在干部监督工作中的应用越来越广泛&#xff0c;为提升监督工作的效率和精准度提供了有力支持。以下是如何利用信息化手段扎实推进干部监督工作的几点建议&#xff1a; 一、搭建信息化平台&#xff0c;实现数据统一管理 要扎实推…

【unity小技巧】unity读excel配置表操作,excel转txt文本,并读取txt文本内容,实例说明

文章目录 前言下载资源库导入资源库excel转txt文本读取txt内容 读取配置表所有的数据&#xff0c;并使用结束语 前言 关于unity读excel配置表操作&#xff0c;其实之前就有用过&#xff0c;这里只是单独整理出这部分知识&#xff0c;后续好使用。 感兴趣可以去看看&#xff1a…

【408精华知识】时钟周期、机器周期、总线周期、指令周期、存取周期还傻傻分不清?

在做题时&#xff0c;我们经常能遇到关于“周期”的表述&#xff0c;比如时钟周期、机器周期、总线周期、指令周期、存取周期&#xff0c;类似的表述让我们很容易迷茫&#xff0c;那么接下来我们就看看它们到底是什么、有什么区别&#xff1f; 周期特点时钟周期也称为CPU时钟周…

太速科技-基于FPGA Spartan6 的双路光纤PCIe采集卡(2路光纤卡)

基于FPGA Spartan6 的双路光纤PCIe采集卡(2路光纤卡) 1、板卡概述   板卡采用xilinx Spartan6系列芯片&#xff0c;支持 PCI Express Base Specification 1.1 x1。内含丰富的逻辑资源和存储单元&#xff0c;板卡FPGA外接双片32M*16bit DDR2缓存器&#xff0c;支持乒乓操作。…

【Linux进程篇】父子进程fork函数||进程生死轮回状态||僵尸进程与孤儿进程

W...Y的主页 &#x1f60a; 代码仓库分享&#x1f495; 前言&#xff1a;上篇文章中我们认识了进程&#xff0c;可执行程序在内存中加载运行被称作进程&#xff0c;而操作系统是通过给每一个可执行程序创建一个PCB来管理进程的。并且学习了一些查看进程的指令&#xff0c;认识…

利用机器非学习进行后门攻击

信息安全是一个古老的计算机领域。许多 80 后还记得自己小时候经常听到的瑞星杀毒和江民杀毒软件。这些 90 年代火遍大江南北的信息安全工具&#xff0c;至今仍然影响着使用互联网和信息技术的千家万户。随着人工智能的兴起和普及&#xff0c;有越来越多的商业软件使用了人工智…

5.2网安学习第五阶段第二周回顾(个人学习记录使用)

本周重点 ①HIDS的基本应用(suricata) ②Suricata的基本应用 ③Suricata的流量检测 ④Suricata的https流量检测 ⑤利用Elastic整合Suricata日志 ⑥利用Wazuh对Suricata主动响应 本周主要内容 ①HIDS的基本应用(suricata) 1、NIDS 1、定义&#xff1a;网络入侵检测系统…

【软件设计师】——6.程序设计语言与语言处理程序

目录 6.1基本概念 6.2编译与解释 6.3文法 6.4有限自动机 6.5正规式 6.6 表达式 6.7 传值与引用 6.8 数据类型与程序控制结构 6.9 程序语言特点 6.10 Java程序设计 6.11 C 6.12 python 6.1基本概念 语句&#xff1a;高级程序设计语言中描述程序的运算步骤、控制结构、…

vue3父组件改变 子组件不改变(uniapp)

项目中遇到了这么个问题 场景&#xff1a;封装select组件&#xff0c;通过子组件选中后传递值给父组件&#xff0c;父组件需要回显这个值&#xff08;这里使用 defineProps和defineEmits就可以实现&#xff0c;或者直接使用defineModel也可以实现&#xff0c;但是uniapp目前不…

语音深度鉴伪识别项目实战:基于深度学习的语音深度鉴伪识别算法模型(一)音频数据编码与预处理

前言 深度学习技术在当今技术市场上面尚有余力和开发空间的&#xff0c;主流落地领域主要有&#xff1a;视觉&#xff0c;听觉&#xff0c;AIGC这三大板块。目前视觉板块的框架和主流技术在我上一篇基于Yolov7-LPRNet的动态车牌目标识别算法模型已有较为详细的解说。与AIGC相关…

vue3父子组件通信,子组件修改父组件传过来的值

一、第一种&#xff0c;通过props方式传值&#xff1a; 父组件&#xff1a; 父组件调用子组件Child1时通过 :msg2 "msg2"把msg2的数据传给子组件&#xff0c;并且通过自定义事件接收子组件的emit通知&#xff0c;用来修改父组件的msg2数据。 源码&#xff1a; &l…

借助 CloudFlare 增强站点内容保护防采集

今天在一位站长的帮助下实测了 CloudFlare 增强站点内容保护实现防采集的功能,效果那是杠杠的,如果您的站点原创内容比较多的话,明月强烈建议试试 CloudFlare 这个内容保护,无论是 WordPress 、Typecho 都有非常好的效果,并且几乎没有任何误伤,搜索引擎爬虫蜘蛛更是不会影…

制作ARM架构 docker镜像

docker简介 docker客户端 Docker 客户端有两种替代选项:名为 docker 的命令行应用程序或名为 Docker Desktop 的基于图形用户界面 (GUI) 的应用程序。 CLI 和 Docker Desktop 均与 Docker 服务器交互。 来自 CLI 或 Docker Desktop 的 docker 命令使用 Docker REST API 将指…

Java与Gradle 的版本兼容性矩阵验证

1.下面这个表格显示了java和gradle的版本兼容性情况 2.根据上面这份表格理解&#xff0c;是不是java17就需要gradle 7.3之后来支持。用android studio 来试验一下: jdk选择: build成功: 说明JDK17并不是一定需要Gradle 7.3之后版本 3.使用JDK1.8、JDK11验证一下Grade 7.2是否可…

如何解决SEO排名上升后遭遇的攻击问题

随着搜索引擎优化&#xff08;SEO&#xff09;策略的成功实施&#xff0c;网站排名的提升往往会引来更多的流量与关注&#xff0c;但同时也可能成为恶意攻击的目标&#xff0c;包括DDoS攻击、SQL注入、XSS攻击等。这些攻击不仅影响用户体验&#xff0c;还可能导致网站降权甚至被…