15. Spring AOP 的实现原理 代理模式

news2024/11/16 10:15:11

目录

1. 代理模式

2. 静态代理 

3. 动态代理

3.1 JDK 动态代理

3.2 CGLIB 动态代理

4. JDK 动态代理和 CGLIB 动态代理对比

5. Spring代理选择

6. Spring AOP 实现原理

6.1 织入

7. JDK 动态代理实现 

8. CGLIB 动态代理实现

9. 总结


1. 代理模式

代理模式:为其他对象提供⼀种 代理 以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另⼀个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

代理模式分为静态代理动态代理。 

2. 静态代理 

静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活(比如接口⼀旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写⼀个代理类)。 实际应用场景非常少,日常开发几乎看不到使用静态代理的场景。
上面我们是从实现和应用角度来说的静态代理,从 JVM 层面来说, 静态代理在编译时就将接⼝、实现类、代理类这些都变成了⼀个个实际的 class 文件

静态代理实现步骤:

  1. 定义⼀个接口及其实现类;
  2. 创建⼀个代理类同样 实现这个接口;
  3. 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。

这样,我们就可以通过代理类屏蔽目标对象的访问,并且可以在目标方法执行前后实现其他的功能。接下来,我们来看一下具体是如何实现静态代理的:

1. 定义接口

2. 实现接口

3. 创建代理类并同样实现支付接口

4. 实际使用

3. 动态代理

相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类( CGLIB 动态代理机制)。

从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中 的。
Spring AOP 的实现依赖了动态代理。
就 Java 来说,动态代理的实现方式有很多种,比如 JDK 动态代理CGLIB 动态代理等等。

3.1 JDK 动态代理

在 Java 动态代理机制中 InvocationHandler 接口 Proxy 类 是核心。
JDK 动态代理类步骤:
  1. 定义⼀个接口及其实现类;
  2. 自定义 InvocationHandler 并重写 invoke 方法 ,在 invoke 方法中会调用原生方法(被代理类的方法)并自定义⼀些处理逻辑;
  3. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[]
    interfaces,InvocationHandler h) 方法创建代理对象;

定义 JDK 动态代理类:

创建一个代理对象并使用:

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

这个方法⼀共有 3 个参数:
  1.  loader :类加载器,用于加载代理对象。
  2.  interfaces : 被代理类实现的⼀些接口;
  3.  h : 实现了 InvocationHandler 接口的对象; 

3.2 CGLIB 动态代理

JDK 动态代理有⼀个最致命的问题是其只能代理实现了接口的类。

 因此,可以通过 CGLIB 动态代理机制来避免。

CGLIB(Code Generation Library)是⼀个基于 ASM 的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知 名的开源框架都使用到 CGLIB,例如 Spring 的 AOP 模块中:如果目标对象 实现了接口 ,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理
在 CGLIB 动态代理机制中 MethodInterceptor 接口 Enhancer 类 是核心。

CGLIB 动态代理类使用步骤:

  1. 定义⼀个类;
  2. ⾃定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
  3. 通过 Enhancer 类的 create()创建代理类。

添加依赖:

和JDK 动态代理不同, CGLIB(Code Generation Library) 实际是属于⼀个开源项目,使用时需要手动添加相关依赖。

<dependency>
 <groupId>cglib</groupId>
 <artifactId>cglib</artifactId>
 <version>3.3.0</version>
</dependency>

定义 MethodInterceptor(方法拦截器):

创建代理类, 并使用:

自定义 MethodInterceptor 并重写 intercept 方法, intercept 用于拦截增强被代理类的方法

  1. obj : 被代理的对象(需要增强的对象)
  2. method : 被拦截的方法(需要增强的方法)
  3. args : 方法入参
  4. proxy : 用于调用原始方法

4. JDK 动态代理和 CGLIB 动态代理对比

  1. JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。
  2. CGLIB 动态代理是通过生成⼀个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final。
性能: 大 部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。

5. Spring代理选择

  1. proxyTargetClass 为 false, 目标实现了接口, 用 jdk 代理
  2. proxyTargetClass 为 false, 目标未实现接口, 用 cglib代理
  3. proxyTargetClass 为 true, 用 cglib 代理

6. Spring AOP 实现原理

Spring AOP 是构建在动态代理基础上,因此 Spring 对 AOP 的支持局限于方法级别的拦截。
Spring AOP 支持 JDK Proxy 和 CGLIB 方式实现动态代理。默认情况下, 实现了接口的类,使
用 AOP 会基于 JDK 生成代理类;没有实现接口的类,会基于 CGLIB 生成代理类。

6.1 织入

织入是把切面应用到目标对象并创建新的代理对象的过程,切面在指定的连接点被织入到目标对
象中。
在目标对象的生命周期里有多个点可以进行织入:
  • 编译期:切⾯在⽬标类编译时被织⼊。这种⽅式需要特殊的编译器。AspectJ的织⼊编译器就
    是以这种⽅式织⼊切⾯的。
  • 类加载期:切⾯在⽬标类加载到JVM时被织⼊。这种⽅式需要特殊的类加载器 (ClassLoader),它可以在⽬标类被引⼊应⽤之前增强该⽬标类的字节码。AspectJ5的加载 时织⼊(load-time weaving. LTW)就⽀持以这种⽅式织⼊切⾯。
  • 运行期:切面在应用运行的某一时刻被织入。一般情况下,在织入切面时,AOP 容器会为目标对象动态创建一个代理对象。Spring AOP 就是以这种方式织入切面的。

7. JDK 动态代理实现 

public interface PayService {
    void pay();
}
public class AliPayService implements PayService{
    @Override
    public void pay(){
        System.out.println("ali pay...");
    }
}
public class PayServiceJDKInvocationHandler implements InvocationHandler {
    // 目标对象就是被代理对象
    private Object target;

    public PayServiceJDKInvocationHandler(Object target) {
        this.target = target;
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("安全检查");
        System.out.println("记录日志");
        System.out.println("记录开始时间");
        // 通过反射调用被代理的方法
        Object retVal = method.invoke(target,args);
        System.out.println("记录结束时间");
        return retVal;
    }

    public static void main(String[] args) {
        PayService target = new AliPayService();
        // 方法调用处理器
        InvocationHandler handler = new PayServiceJDKInvocationHandler(target);
        // 创建一个代理类:通过被代理类、被代理实现的接口、方法调用处理器来创建
        PayService proxy =(PayService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                new Class[]{PayService.class},
                handler);
        proxy.pay();
    }
}

8. CGLIB 动态代理实现

public interface PayService {
    void pay();
}
public class PayServiceCGLIBInterceptor implements MethodInterceptor {
    // 被代理对象
    private Object target;

    public PayServiceCGLIBInterceptor(Object target) {
        this.target = target;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("安全检查");
        System.out.println("记录日志");
        System.out.println("记录开始时间");
        // 通过反射调用被代理的方法
        Object retVal = methodProxy.invoke(target,args);
        System.out.println("记录结束时间");
        return retVal;
    }

    public static void main(String[] args) {
        PayService target = new AliPayService();
        PayService proxy =(PayService) Enhancer.create(target.getClass(),
                new PayServiceCGLIBInterceptor(target));
        proxy.pay();
    }
}

9. 总结

AOP 是对某方面能力的统⼀实现,它是⼀种实现思想,Spring AOP 是对 AOP 的具体实现,Spring AOP 可通过 AspectJ(注解)的方式来实现 AOP 的功能,Spring AOP 的实现步骤是:
  1. 添加 AOP 框架支持;
  2. 定义切面和切点;
  3. 定义通知。
Spring AOP 是通过动态代理的方式,在运行期将 AOP 代码织入到程序中的,它的实现方式有两种: JDK Proxy 和 CGLIB。

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

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

相关文章

<Git/Gerrit>版本控制Git以及代码评审Gerrit常见的开发操作

下载安装,环境变量配置直接百度; 1.代码拉取: 操作步骤&#xff1a;在正确配置完git的条件下:在本地文件夹下&#xff1a;右键–Git Bash -Here&#xff1a; 出现如下弹窗: 在黑窗口输入:代码拉取路径(一般都是把命令和路径在外面写好,直接粘贴(在窗口右键,Paste,回车)) 代码…

linux系统磁盘性能监视工具iostat

目录 一、iostat介绍 二、命令格式 三、命令参数 四、参考命令&#xff1a;iostat -c -x -k -d 1 &#xff08;一&#xff09;输出CPU 属性值 &#xff08;二&#xff09;CPU参数分析 &#xff08;三&#xff09;磁盘每一列的含义 &#xff08;四&#xff09;磁盘参数分…

AI生成式视频技术来临:Runway Gen-2文本生成视频

Runway Gen-2的官方网站提供了一种文本生成视频的工具。以下是对该工具的介绍&#xff1a; 文本生成视频&#xff1a;Runway Gen-2是一个创新的在线工具&#xff0c;可以将文本转化为视频。用户只需输入文本描述或句子&#xff0c;Runway Gen-2就能自动生成相应的视频内容。这…

uni-ajax网络请求库使用

uni-ajax网络请求库使用 uni-ajax是什么 uni-ajax是基于 Promise 的轻量级 uni-app 网络请求库,具有开箱即用、轻量高效、灵活开发 特点。 下面是安装和使用教程 安装该请求库到项目中 npm install uni-ajax编辑工具类request.js // ajax.js// 引入 uni-ajax 模块 import ajax…

【最短路算法】SPFA

引入 在计算机科学的世界里&#xff0c;算法就像是星空中的繁星&#xff0c;各自闪烁着智慧的光芒。它们沉默而坚定&#xff0c;像是一群不语的哲人&#xff0c;默默地解答着世界的问题。 算法的步骤&#xff0c;如同优美的诗行&#xff0c;让复杂的问题在流转的字符中得以释…

PHP8的常量-PHP8知识详解

常量和变量是构成PHP程序的基础&#xff0c;在PHP8中常量的这一节中&#xff0c;主要讲到了定义常量和预定义常量两大知识点。 一、定义常量 定义常量也叫声明常量。在PHP8中&#xff0c;常量就是一个标识符&#xff08;名字&#xff09;&#xff0c;一旦定义&#xff08;声明&…

ansible-playbook编写 lnmp 剧本

ansible-playbook编写 lnmp 剧本 vim /opt/lnmp/lnmp.yaml执行剧本 ansible-playbook lnmp.yaml

WebDAV之π-Disk派盘 + DEVONthink

DEVONthink是由一家来自德国的老牌软件开发商发布的「知识管理」软件,运行于 Mac/iOS 平台。官方自己定位为全方位(中文环境下略有遗憾)帮助你实现知识管理,可以称之为“模块级”应用了。 DEVONthink还支持各种云服务同步,文件管理您的终极文件管理应用、文件、图片与连接远…

Android Ble蓝牙App(一)扫描

Ble蓝牙App&#xff08;一&#xff09;扫描 前言正文一、基本配置二、扫描准备三、扫描页面① 增加UI布局② 点击监听③ 扫描处理④ 广播处理 四、权限处理五、扫描结果① 列表适配器② 扫描结果处理③ 接收结果 六、源码 前言 关于低功耗的蓝牙介绍我已经做过很多了&#xff0…

基于SHARC+®单核的ADSP-21567KBCZ6、ADSP-21566BBCZ4、ADSP-21566KBCZ4高性能DSP处理器产品

ADSP-2156x 处理器的速度高达 1 GHz&#xff0c;属于 SHARC 系列产品。ADSP-2156x 处理器基于 SHARC 单核。ADSP-2156x SHARC 处理器是 SIMD SHARC 系列数字信号处理器 (DSP) 中的一款产品&#xff0c;采用 ADI 的超级哈佛架构。这些 32 位/40 位/64 位浮点处理器已针对高性能音…

Practice3|922. 按奇偶排序数组 II、143. 重排链表

922. 按奇偶排序数组 II 1.题目&#xff1a; 给定一个非负整数数组 nums&#xff0c; nums 中一半整数是 奇数 &#xff0c;一半整数是 偶数 。 对数组进行排序&#xff0c;以便当 nums[i] 为奇数时&#xff0c;i 也是 奇数 &#xff1b;当 nums[i] 为偶数时&#xff0c; i…

将临时表的所有数据添加到另一张表的某个字段

临时表 目标表 SmsCatagory smsCatagory new SmsCatagory(); smsCatagory.setGasStationId(); smsCatagory.setType(); ...... sendTaskService.batchInsertByTemp(smsCatagory);xml&#xff1a; <insert id"batchInsertByTemp">INSERT INTO cs_sms_cata…

[每日习题]进制转换 参数解析——牛客习题

hello,大家好&#xff0c;这里是bang___bang_&#xff0c;本篇记录2道牛客习题&#xff0c;进制转换&#xff08;简单&#xff09;&#xff0c;参数解析&#xff08;中等&#xff09;&#xff0c;如有需要&#xff0c;希望能有所帮助&#xff01; 目录 1️⃣进制转换 2️⃣参…

safe敏捷项目管理,safe框架简介

一、SAFe简介 SAFe是ScaledAgile Framework的简称&#xff0c;由DeanLeffingwell创建&#xff0c;SAFe&#xff08;Scaled Agile Framework&#xff09;是全球运用最广泛的大规模敏捷框架。 SAFe融合了精益、敏捷和DevOps&#xff0c;它是一个知识库&#xff0c;囊括了大量已…

【面试】某公司记录一次面试题

文章目录 框架类1. Spring boot与 spring 架相比&#xff0c;好在哪里?2. Spring boot以及 Spring MVC 常用注解(如requestingMapping&#xff0c;responseBody 等)3. 常用的java 设计模式&#xff0c;spring 中用到哪些设计模式4. SpringIOC是什么&#xff0c;如何理解5. AOP…

v-model绑定checkbox无法动态更新视图

在vue2中使用v-model绑定checkbox <input type"checkbox" v-model"isChecked" :valueisChecked change"handleCheckboxChange" />监听change事件&#xff0c;并在change事件中做一些特殊处理&#xff0c;比如用户在登录时有没有阅读过隐私…

C语言文件io操作

一、fopen 在C语言中&#xff0c;操作文件之前应该先打开文件。使用<stdio.h>头文件中的fopen()函数可以打开文件&#xff0c;因为FILE也是结构体&#xff0c;我们通过返回一个文件指针就可以对文件进行操作。在用完fopen之后要记得关闭该文件流。 用法&#xff1a; F…

小目标检测(3)——msgqueue多线程通信和多线程互斥编程

文章目录 引言正文代码的执行和线程使用std::mutex进行编程mutex基本用法std::lock_guard的使用std::unique_lock的使用 condition_variable的使用wait函数的使用condition_variable的整体代码 多线程编程的基本语句 总结引用 引言 在学习老师给的目标检测的代码过程中&#xf…

红队攻防 | 解决HW被疯狂封IP姿势~(附下载)

本项目其实就是个简单的代理服务器&#xff0c;经过小小的修改。加了个代理池进来。渗透、爬虫的时候很容易就会把自己ip给ban了&#xff0c;所以就需要ip代理池了。 ProxyPool 爬虫代理IP池项目,主要功能为定时采集网上发布的免费代理验证入库&#xff0c;定时验证入库的代理…

排序算法(冒泡排序、选择排序、插入排序、希尔排序、堆排序、快速排序、归并排序、计数排序)

&#x1f355;博客主页&#xff1a;️自信不孤单 &#x1f36c;文章专栏&#xff1a;数据结构与算法 &#x1f35a;代码仓库&#xff1a;破浪晓梦 &#x1f36d;欢迎关注&#xff1a;欢迎大家点赞收藏关注 文章目录 &#x1f353;冒泡排序概念算法步骤动图演示代码 &#x1f34…