【Spring】Spring AOP原理

在这里插入图片描述

文章目录

  • 前言
  • 代理模式
    • 静态代理
    • 动态代理
      • JDK动态代理
      • CGLib 动态代理
  • 总结

前言

前面我们学习了关于 Spring AOP 的使用,那么今天这篇文章,我们将深入理解 Spring AOP 的原理,也就是 Spring 是如何实现 AOP 的。

Spring AOP 是基于动态代理来实现 AOP 的,那么什么是代理呢?这里的代理其实是一种模式——代理模式。

代理模式

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

代理模式的主要作用是扩展目标对象的功能,例如在目标对象的某个方法执行前后可以增加一些自定义的操作。此外,代理模式还可以结合享元模式以减少存储器用量,例如创建一个复杂对象及多个代理者,每个代理者会引用到原本的复杂对象,作用在代理者的运算会转送到原本对象。一旦所有的代理者都不存在时,复杂对象会被移除。

这是使用代理之前:

在这里插入图片描述

使用代理之后:

在这里插入图片描述
我们生活中最常见的可以体现代理模式的就是房屋中介了,当我们想要租房子的时候,如果我们想要直接找到房东的话,会很难且麻烦,因为我们不知道哪一间房子正在出租,如果一个一个问的话就需要很多时间,所以这个时候我们的选择往往就是寻找房屋中介,告诉中介的诉求了之后,房屋中介就会根据我们的需求去寻找合适的房主,这样就极大的方便了我们租房的过程。

代理模式中的主要角色:

  1. Subject:业务接口类,可以是抽象类或者接口(不一定有)
  2. RealSubject:业务实现类。具体的业务执行,也就是被代理对象,上面的例子中房东就是体现
  3. Proxy:代理类。RealSubject的代理,通过中介体现

在这里插入图片描述

代理模式可以分为静态代理和动态代理。静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活且麻烦。而动态代理则是通过在运行时创建一个子类实例来实现的,可以更加灵活地实现代理。

  • 静态代理:有程序员创建代理类或特定工具自动生成源代码再对其进行编译,在程序运行前代理类的 .class 文件就已经存在了。
  • 动态代理:在程序运行时,运用反射机制动态创建而成

在实际开发中,代理模式可以帮助降低主要业务与辅助业务的耦合度,同时提高辅助业务代码的重用度。

静态代理

静态代理是指代理类在程序运行前就已经定义好,其与目标类的关系在程序运行前就已经确立。在静态代理中,代理类和目标类都实现相同的接口,代理类通过调用目标类的方法来实现接口中定义的业务逻辑,同时可以在调用前后增加额外的操作。

根据租房子的案例来讲就是:在用户租房子之前 ,我中介和房东就已经协商好了,中介就知道你房东要出租房子还是出售房子,然后当客户有相同的需求的话,我中介就可以直接联系客户和房东进行对接。通过代码展示就是这样的:

定义接口(房东要做的事,也就是中介要做的主要的事):

public interface HouseSubject {void rentHouse();
}

实现接口(房东出租房子):

public class RealHouseSubject implements HouseSubject {@Overridepublic void rentHouse() {System.out.println("我是房东,我要出租房子...");}
}

代理(中介帮房东出租房子):

public class HouseProxy implements HouseSubject {//将被代理对象声明为成员变量private HouseSubject subject;public HouseProxy(RealHouseSubject realHouseSubject) {this.subject = realHouseSubject;}@Overridepublic void rentHouse() {System.out.println("我是中介,开始代理");subject.rentHouse();System.out.println("我是中介,结束代理");}
}

客户租房子:

public class Main {public static void main(String[] args) {RealHouseSubject subject = new RealHouseSubject();HouseProxy proxy = new HouseProxy(subject);proxy.rentHouse();}
}

在这里插入图片描述
通过这个过程,客户就通过中介成功找到了房东租房子,但是呢?由于这是静态代理,如果我们的房东之前已经和中介商量好了要出租房子,但是这时房东又想出租+出售房子,那么这时候房东就需要和中介再进行沟通,通过我们代码的体现就是这样:

接口:

public interface HouseSubject {void rentHouse();void saleHouse();
}

被代理对象:

public class RealHouseSubject implements HouseSubject {@Overridepublic void rentHouse() {System.out.println("我是房东,我要出租房子...");}@Overridepublic void saleHouse() {System.out.println("我是房东,我要出售房子");}
}

代理对象:

public class HouseProxy implements HouseSubject {//将被代理对象声明为成员变量private HouseSubject subject;public HouseProxy(RealHouseSubject realHouseSubject) {this.subject = realHouseSubject;}@Overridepublic void rentHouse() {System.out.println("我是中介,开始代理");subject.rentHouse();System.out.println("我是中介,结束代理");}@Overridepublic void saleHouse() {System.out.println("我是中介,开始代理");subject.saleHouse();System.out.println("我是中介,结束代理");}
}

客户需要买房子:

public class Main {public static void main(String[] args) {RealHouseSubject subject = new RealHouseSubject();HouseProxy proxy = new HouseProxy(subject);proxy.saleHouse();}
}

在这里插入图片描述
由此可以看出,当我们需要修改业务的时候,既需要修改接口,也需要修改RealSubject 类,还需要修改代理类,那么是否有一种方法,可以使用一种代理类来实现变动业务的代理呢?是可以的,这时就需要用到我们的代理类了。

动态代理

动态代理是一种在运行时动态生成代理对象的技术,它通过在程序运行时创建代理对象来间接访问原始对象,并在访问前后执行额外的操作。动态代理通常用于在不修改原始对象的情况下增强对象的功能,例如实现横切关注点(cross-cutting concerns)如日志记录、性能监控、事务管理等。

在动态代理中,代理类在程序运行期间创建代理对象,而不是在代码中事先定义好。代理对象中的方法是对目标对象方法的增强方法,可以在方法执行前后插入额外的逻辑。动态代理能够根据需要在运行时动态生成代理对象,因此可以更加灵活地扩展对象的功能。

常见的动态代理有JDK代理和CGLib代理,分别可以针对实现了接口的类代理以及没有实现接口的类代理。

JDK动态代理

JDK动态代理实现的关键步骤如下:

  1. 定义接口:首先定义一个接口,该接口将由目标对象和代理对象实现。
  2. 创建目标对象:创建目标对象的实例,该实例将被代理对象代理。
  3. 实现InvocationHandler接口:创建一个实现了java.lang.reflect.InvocationHandler接口的类,该类将定义代理对象的方法4调用逻辑。在该类中,需要重写invoke()方法,以便在目标对象的方法调用前后执行额外的操作。
  4. 创建代理对象:使用java.lang.reflect.Proxy类和InvocationHandler对象创建代理对象。具体来说,通过Proxy类的静态方法newProxyInstance()创建一个代理实例,该实例将调用InvocationHandler对象的invoke()方法来处理方法调用。
  5. 使用代理对象:使用代理对象来调用目标对象的方法,在方法调用前后会执行额外的逻辑。

(❁´◡`❁)定义接口

public interface HouseSubject {void rentHouse();void saleHouse();
}

(❁´◡`❁)创建目标对象

public class RealHouseSubject implements HouseSubject {@Overridepublic void rentHouse() {System.out.println("我是房东,我要出租房子...");}@Overridepublic void saleHouse() {System.out.println("我是房东,我要出售房子");}
}

(❁´◡`❁)实现 InvocationHandler 接口

public class JDKInvocationHandler implements InvocationHandler {//目标对象/被代理对象private Object target;public JDKInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("我是中介,开始代理...");//通过反射调⽤被代理类的⽅法Object retVal = method.invoke(target,args);System.out.println("我是中介,结束代理...");return retVal;}
}

(❁´◡`❁)创建代理对象并使用

public class Main {public static void main(String[] args) {HouseSubject target = new RealHouseSubject();//创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(target.getClass().getClassLoader(),new Class[]{HouseSubject.class},new JDKInvocationHandler(target));proxy.saleHouse();}
}

在这里插入图片描述
InvocationHandler 接⼝是Java动态代理的关键接⼝之⼀,它定义了⼀个单⼀⽅法 invoke() ,⽤于处理被代理对象的⽅法调⽤:

public interface InvocationHandler {/*** 参数说明* proxy:代理对象* method:代理对象需要实现的⽅法,即其中需要重写的⽅法* args:method所对应⽅法的参数*/public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

通过实现 InvocationHandler 接⼝,可以对被代理对象的⽅法进⾏功能增强

Proxy 类中使⽤频率最⾼的⽅法是: newProxyInstance() ,这个⽅法主要⽤来⽣成⼀个代理对象

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException{}
  • Loder:类加载器,用于加载代理对象
  • interfaces:被代理类实现的⼀些接⼝(这个参数的定义,也决定了JDK动态代理只能代理实现了接⼝的⼀些类)
  • h:实现了 InvocationHandler 接口的对象

CGLib 动态代理

CGLib是另一个流行的Java动态代理框架,它扩展了Java的反射机制,并提供了更高级的功能。CGLib可以用来创建具有完全功能的代理类,并且能够代理没有接口的类。

CGLib动态代理实现的关键步骤如下:

  1. 下载CGLib库:首先需要下载CGLib库,并将其添加到项目的类路径中。
  2. 创建目标对象:创建一个目标对象的实例,该实例将被代理对象代理。
  3. 创建Enhancer对象:创建一个org.easymock.classextension.Enhancer对象,用于创建代理对象。Enhancer类是CGLib的核心类,用于生成代理类和实例化代理对象。
  4. 设置代理类和回调函数:通过调用Enhancer对象的setSuperclass()方法设置代理类的父类为目标类,然后通过setCallback()方法设置回调函数。回调函数是一个实现了目标类方法的拦截器对象,用于拦截目标方法调用并执行额外的逻辑。
  5. 创建代理对象:通过调用Enhancer对象的create()方法创建代理对象。
  6. 使用代理对象:使用代理对象来调用目标对象的方法,在方法调用前后会执行额外的逻辑。

与JDK动态代理相比,CGLib提供了更多的灵活性和功能。它支持代理没有接口的类,并且能够动态地修改和扩展类的行为。CGLib还支持方法级别的拦截和过滤,可以更加精细地控制目标方法的调用。因此,CGLib在许多场景中得到了广泛应用,例如AOP编程、测试框架、远程调用等。

(❁´◡`❁)添加依赖

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

(❁´◡`❁)⾃定义MethodInterceptor(⽅法拦截器,并且实现MethodInterceptor接⼝)

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;public class CGLibInterceptor implements MethodInterceptor {private Object target;public CGLibInterceptor(Object target) {this.target = target;}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("我是中介,开始代理...");Object retVal = methodProxy.invoke(target,objects);System.out.println("我是中介,结束代理...");return retVal;}
}

(❁´◡`❁)创建类并使用

public class Main {public static void main(String[] args) {HouseSubject target = new RealHouseSubject();HouseSubject proxy = (HouseSubject) Enhancer.create(target.getClass(),new CGLibInterceptor(target));proxy.saleHouse();}
}

在这里插入图片描述

总结

总结一下:
1. Spring AOP 是如何实现的?

  • Spring AOP(Aspect-Oriented Programming)通过动态代理实现了面向切面编程。具体来说,Spring在创建某个bean的时候,实际上创建的是这个bean的一个代理对象。后续对bean中方法的调用,实际上调用的是代理类重写的代理方法。

2. 动态代理是如何实现的?

  • 参考上文

3. Spring使用的是 JDK 动态代理和 CGLib 代理中的哪一个?

  • 两个都用了

4. 什么时候使用 JDK 代理,什么时候使用 CGLib 代理?

  • 在Spring中,JDK动态代理和CGLib动态代理的选择取决于目标对象的类型。

    1. 当目标对象实现了接口时,Spring会使用JDK动态代理。这是因为JDK动态代理通过实现java.lang.reflect.InvocationHandler接口来创建代理对象,并且使用目标对象的接口来定义代理对象的行为。这种情况下,Spring会在运行时动态地创建一个实现了目标对象接口的代理类,该代理类将拦截对目标对象的调用并执行额外的逻辑。
    1. 当目标对象没有实现接口时,Spring会使用CGLib动态代理。CGLib是一个代码生成库,可以在运行时动态地创建一个目标对象的子类,并重写目标类的方法以实现额外的逻辑。这种情况下,Spring会在运行时动态地创建一个目标对象的子类,该子类将拦截对目标对象的调用并执行额外的逻辑。

5. JDK 代理和 CGLib 代理有什么区别?

  1. 实现方式:JDK代理是基于接口实现的,因此必须先定义接口;而CGLib代理则直接基于被代理类实现,不需要定义接口。
  2. 代理机制:JDK动态代理机制是委托机制,通过委托handler调用原始实现类方法;而CGLib则使用继承机制,被代理类和代理类是继承关系,因此代理类对象可以赋值给被代理类类型的变量。
  3. 对final方法的处理:由于CGLib的原理是动态生成被代理类的子类,因此无法对声明为final的方法进行代理。

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

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

相关文章

matlab抽取与插值

什么是抽取&#xff1f; 我们假设一个数字信号 x ( n ) , n 1 , 2 , . . . , N x(n),n1,2,...,N x(n),n1,2,...,N共有 N N N个点&#xff0c;抽取就是每个几个点抽1个点&#xff0c;比如2倍抽取&#xff0c;那么抽取后的信号为 y ( n ) , y ( 1 ) x ( 1 ) , y ( 2 ) x ( 3 …

IO 专题

使用try-with-resources语句块&#xff0c;可以自动关闭InputStream [实践总结] FileIUtils 共通方法最佳实践 [实践总结] java 获取在不同系统下的换行符 [实践总结] StreamIUtils 共通方法最佳实践 斜杠“/“和反斜杠“\“的区别 路径中“./”、“…/”、“/”代表的含义…

你真的会数据结构吗:顺序表

❀❀❀ 文章由不准备秃的大伟原创 ❀❀❀ ♪♪♪ 若有转载&#xff0c;请联系博主哦~ ♪♪♪ ❤❤❤ 致力学好编程的宝藏博主&#xff0c;代码兴国&#xff01;❤❤❤ 又和大家见面啦&#xff01;在大家看到这个标题的时候其实就已经发现了&#xff1a;我们的C语言的基础知识大…

【GitHub项目推荐--不错的 Go开源项目】【转载】

开源实时性能分析平台 Pyroscope 是基于 Go 的开源实时性能分析平台&#xff0c;在源码中添加几行代码 pyroscope 就能帮你找出源代码中的性能问题和瓶颈、CPU 利用率过高的原因&#xff0c;调用树展示帮助你理解程序&#xff0c;支持 Go、Python、Ruby 语言。 Pyroscope 可以…

java程序判等问题

注意 equals 和 的区别 对基本类型&#xff0c;比如 int、long&#xff0c;进行判等&#xff0c;只能使用 &#xff0c;比较的是直接值。因为基本类型的值就是其数值。对引用类型&#xff0c;比如 Integer、Long 和 String&#xff0c;进行判等&#xff0c;需要使用 equals 进…

智能解决方案——体脂秤芯片CSU18M91

现在的年轻人爱健身&#xff0c;十分关注身材、形体&#xff0c;减肥、健身成了生活日常&#xff1b;中老年人则关注健康指数、有无病症&#xff0c;实时把握身体情况。现在一台体脂称通过测试体重、体脂、BMI、水分等数据并给出相应提示&#xff0c;并且许多人都将体脂检测数据…

MySQL分组,获取组内最新的10条数据

一、记录 记录一次SQL&#xff0c;最近在项目中遇到了一个相对比较复杂的SQL。 要求依据分组&#xff0c;获取每个分组后的前10条数据。 分组查询最新的数据&#xff0c;应该都做过&#xff0c;但是获取前10条数据&#xff0c;还是没处理过的。 二、处理 2.1 前期数据准备 …

开发知识点-Flutter移动应用开发

支持 安卓 IOS Android 鸿蒙 第一章dart基础章节介绍 移动电商——Flutter-广告Banner组件制作 移动电商——Flutter实战课程介绍 Flutter实例——路由跳转的动画效果

Elment UI的el-table-column表头旁边有点击按钮类似的操作

Elment UI的el-table-column表头旁边有点击按钮类似的操作 <el-table-column fixed"right" label"操作" ><!-- 表头 --> {{-- <template slot"header" header"scope">--}} {{-- <span…

精通 VS 调试技巧,学习与工作效率翻倍!

​ ✨✨ 欢迎大家来到贝蒂大讲堂✨✨ ​ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; ​ 所属专栏&#xff1a;C语言学习 ​ 贝蒂的主页&#xff1a;Betty‘s blog 1. 什么是调试 当我们写代码时候常常会遇见输出结果不符合我们预…

单轴测径仪在线缆电缆测控中的应用

线缆电缆在生产中能进行品质测量与控制&#xff0c;对其生产模式而言&#xff0c;是更为合适的&#xff0c;毕竟其生产模式决定了其任何品质问题出现都会造成产品的品质下降或不合格。要想进行生产高品质的产品&#xff0c;对产线的要求较高。 单轴测径仪作为测控一体&#xff…

安卓开发之自动缩放布局

AutoScalingLayout 适用于 Android 的自动缩放布局。 替换布局&#xff1a; 我们只需要替换根布局所需的自动缩放&#xff0c;子布局也将实现自动缩放。 原始布局AutoScalingLayout相对布局ASRelativeLayout线性布局ASLinearLayoutFrameLayout&#xff08;框架布局&#xff…

项目一:踏上Java开发之旅

文章目录 一、实战概述二、实战步骤任务1&#xff1a;安装配置JDK并开发第一个Java程序步骤一&#xff1a;安装JDK步骤二&#xff1a;配置JDK环境变量步骤三&#xff1a;开发第一个Java程序 课堂练习任务1、打印个人信息任务2、打印直角三角形任务3、打印一颗爱心任务4、打印史…

Linux服务器系统修改SSH端口教程

修改端口号是通过修改SSH的配置文件实现的&#xff0c;在服务器终端先激活root用户&#xff0c;然后输入&#xff1a; vim /etc/ssh/sshd_config找到#Port 22这个位置 键盘按i进入编辑模式 删除掉Port 22前面的#&#xff0c;然后键盘按一下回车键&#xff08;如果没有#可不必…

记一次SPI机制导致的BUG定位【不支持:http://javax.xml.XMLConstants/property/accessExternalDTD】

1、前因 今天在生产环境启用了某个功能&#xff0c;结果发现有个文件上传华为云OBS失败了&#xff0c;报错如下&#xff1a; Caused by: java.lang.IllegalArgumentException: 不支持&#xff1a;http://javax.xml.XMLConstants/property/accessExternalDTDat org.apache.xal…

C++参悟:数值运算相关

数值运算相关 一、概述二、常用数学函数1. 基础运算1. 浮点值的绝对值&#xff08; |x| &#xff09;2. 浮点除法运算的余数3. 除法运算的有符号余数4. 除法运算的有符号余数和最后三个二进制位5. 混合的乘加运算6. 两个浮点值的较大者7. 两个浮点值的较小者8. 两个浮点值的正数…

Grafana loki配置, 无脑版

使用docker部署Grafana loki 1.创建 docker-compose.yml 文件 touch docker-compose.yml写入以下内容 vim touch docker-compose.yml version: "3"networks:loki:services:loki:image: grafana/loki:latestrestart: unless-stoppedports:- "3100:3100"vo…

LeetCode 14.最长公共前缀(python版)

需求 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串 “”。 示例 1&#xff1a; 输入&#xff1a;strs [“flower”,“flow”,“flight”] 输出&#xff1a;“fl” 示例 2&#xff1a; 输入&#xff1a;strs [“dog”,“race…

【高效开发工具系列】Intellj IDEA 2023.3 版本

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

curl命令导致你下载的文件为空原因分析

文章目录 1.前言2. 通过curl -O 下载远端文件2.1 执行curl -O下载远端文件2.2 通过curl -v 查看详细的请求和响应的信息 3.通过在curl -O 中增加 -L 参数保证curl能够自动跟踪和请求远端返回的重定向地址4.结论 1.前言 最近在进行线上项目调试的过程中需要安装调试工具&#xf…