14. Spring AOP(二)实现原理

源码位置:spring_aop

上一篇文章中我们主要学习了AOP的思想和Spring AOP使用,本文讲的是Spring是如何实现AOP的,Spring AOP是基于动态代理来实现AOP的,在将动态代理之前先来了解一下什么是代理模式。

1. 代理模式

在现实中就有许多代理的例子:比如你想找一个明星帮你拍广告,明星只对他的代理人开放权限,你就需要跟他的代理人去沟通,由代理人来代替明星完成收钱+售后的操作,然而拍广告这件事还是明星去做的。

1.1 代理模式的定义

定义: 代理模式也叫委托模式,是一种设计模式,它为其他对象提供一种代理控制对这个对象的访问,它的作用就是通过一个代理类,让我们在调用目标对象方法的时候,不再是直接对该方法进行调用,而是通过代理对象间接调用

解释: 在某些情况下,调用方不太适合直接访问目标对象的方法(没开放权限),就需要使用这种设计模式来使用一个代理对象在调用方和目标对象之间起到中介作用,代理模式在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强(Advice)。这里的增强和上一篇Spring AOP中的通知(Advice)是一样的,本章节将Advice全都统称为增强。

代理模式的主要角色:

  • Subject:业务接口类,可以是抽象类或者接口(不一定有)
  • RealSubject:目标对象(被代理对象),也就是具体的业务执行
  • Proxy:代理对象,RealSubject的代理

Untitled Diagram.drawio-2.png

1.2 代理模式的实现

代理模式的实现分为静态代理动态代理

1.2.1 静态代理

定义:在程序运行前,代理类的.class文件就已经存在了,也就是提前把所有方法的代理业务全都写好在程序里。

接下来我通过代码的方式,以明星接广告为例来加深理解:

  1. 定义接口(定义拍广告的业务)
public interface Subject {void takeAdvertise();
}
  1. 实现接口(由明星来实现拍广告这个业务)
public class RealSubject implements Subject{@Overridepublic void takeAdvertise() {System.out.println("我是明星,我来拍广告");}
}
  1. 实现代理(代理人,帮明星处理业务中的额外操作)
public class Proxy implements Subject{private final RealSubject realSubject;public Proxy(RealSubject realSubject) {this.realSubject = realSubject;}@Overridepublic void takeAdvertise() {System.out.println("我是代理人,开始收钱");realSubject.takeAdvertise();System.out.println("我是代理人,提供售后");}
}

测试代码:模拟代理模式下你通过Proxy(代理对象),访问目标对象(RealSubject)

public class Application {public static void main(String[] args) {Proxy proxy = new Proxy(new RealSubject());proxy.takeAdvertise();}
}

运行结果:

我是代理人,开始收钱
我是明星,我来拍广告
我是代理人,提供售后

这就是一个静态的代理模式的实现,静态代理模式的劣势也很明显,当你需要新增一个业务的时候,比如找明星开演唱会的时候,他的代理人做的事情同样是收钱+售后,你就需要继续编写下面代码:

  1. 定义接口:
public interface Subject {void takeAdvertise();void giveConcert();
}
  1. 实现接口:
public class RealSubject implements Subject{@Overridepublic void takeAdvertise() {System.out.println("我是明星,我来拍广告");}@Overridepublic void giveConcert() {System.out.println("我是明星,我来开演唱会");}
}
  1. 实现代理:
public class Proxy implements Subject{private final RealSubject realSubject;public Proxy(RealSubject realSubject) {this.realSubject = realSubject;}@Overridepublic void takeAdvertise() {System.out.println("我是代理人,开始收钱");realSubject.takeAdvertise();System.out.println("我是代理人,提供售后");}@Overridepublic void giveConcert() {System.out.println("我是代理人,开始收钱");realSubject.giveConcert();System.out.println("我是代理人,提供售后");}
}
  1. 运行结果:
我是代理人,开始收钱
我是明星,我来开演唱会
我是代理人,提供售后

这时候我们发现代理对象出现冗余代码,既然代理对象做的都是收钱+售后两件事,我们是否有办法消除这些冗余代码呢?这时候我们就要提到动态代理了。

1.2.2 动态代理

定义:在程序运行时,运用反射机制动态创建而成

相对于静态代理来说,它更加灵活:我们不需要针对每个目标对象业务的增加而去修改代理对象中的代码,而是把创建代理对象的工作推迟到程序运行时由JVM来实现,也就是说动态代理在程序的运行时,根据需要动态创建生成。

Java对动态代理进行了实现,并提供了一些API,常见的实现方式有两种:

  • JDK动态代理
  • CGLIB动态代理

JDK动态代理
  1. 编写代理类:通过JDK动态代理的提供的接口实现一个新的代理类
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("JDK动态代理开始");Object result = method.invoke(target, args);System.out.println("JDK动态代理结束");return result;}
}

解释一下代理类:

  • 代理类中同样需要一个目标对象target,这里我们使用Object得以接收不同的目标对象
  • 实现 java.lang.reflect.InvocationHandler接口,并实现invoke方法
  • invoke()方法的作用是调用目标方法并对方法进行增强,代理对象增强的内容和目标方法的逻辑在该方法的代码块中得以体现
  • invoke()方法中有三个参数Object proxy, Method method, Object[] args
    • proxy: 表示生成的代理对象,通常不直接使用
    • method: 是反射机制里的对象,该对象可以通过类的class对象+方法名来获取,通过该对象可以调用目标方法
    • args: 参数列表,用于传入目标方法中需要的参数
  1. 获取代理对象并使用:
public class Application {public static void main(String[] args) {//动态代理//目标类RealSubject target = new RealSubject();//根据代理类获取代理对象Subject proxy = (Subject) Proxy.newProxyInstance(target.getClass().getClassLoader(),new Class[] {Subject.class},new JDKInvocationHandler(target));proxy.giveConcert();proxy.takeAdvertise();}
}

解释下上述过程如何获取代理对象的:

获取代理对象需要使用java.lang.reflect.Proxy类中的newProxyInstance()方法,在该方法中需要传入三个参数:

  • 类加载器: 在使用动态代理时,通常建议传入目标对象的类加载器作为参数,以确保代理对象能够正常访问目标对象的方法。
  • Class数组: 这是一个包含目标对象所实现的接口的数组,代理对象将实现这些接口,并将方法调用委托给 JDKInvocationHandler(下一个参数)
  • InvocationHandler: 这是一个实现了 InvocationHandler 接口的对象,用于处理代理对象的方法调用。这里传入了一个JDKInvocationHandler 对象,它将拦截代理对象的方法调用,并在方法执行前后执行额外的逻辑。

JDK动态代理的局限性:代理类和目标类都必须实现相同的接口,因此只能代理接口类,如果把目标类为普通类的话就会报错,而CGLIB动态代理恰巧可以弥补这方面的不足,接下来我们就讲讲GCLIB。

CGLIB动态代理

CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,允许我们在运行时对字节码进行修改和动态生成,CGLIB通过继承方式实现代理。

  1. 导入GCLIB的依赖:
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
  1. 实现接口类:实现MethodInterceptor接口,使用起来和JDK动态代理差不多,这里就不赘述了。
public class CGLibMethodInterceptor implements MethodInterceptor {private Object target;public CGLibMethodInterceptor(Object target) {this.target = target;}/**** @param o 代理类* @param method 目标方法* @param objects 参数列表* @param methodProxy*/@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("CGLib动态代理开始");Object result = method.invoke(target, objects);System.out.println("CGLib动态代理结束");return result;}
}
  1. 这里我新创建一个不实现接口的普通目标对象
public class RealSubject2 {public void takeAdvertise() {System.out.println("我是明星,我来拍广告");}public void giveConcert() {System.out.println("我是明星,我来开演唱会");}
}
  1. 获取代理对象并使用,与JDK获取代理对象的差别如下:
  • JDK中必须使用一个接口类来接收代理对象,GCLib直接使用目标类来接收
  • 创建的时候调用net.sf.cglib.proxy.Enhancer类的creat()方法
  • create()方法中少传了一个class[]接口列表
public class Application {public static void main(String[] args) {//CGLIB动态代理RealSubject2 target = new RealSubject2();RealSubject2 proxy = (RealSubject2) Enhancer.create(target.getClass(),new CGLibMethodInterceptor(target));proxy.takeAdvertise();proxy.giveConcert();}
}
  1. 成功对普通目标对象的目标方法进行增强:
CGLib动态代理开始
我是明星,我来拍广告
CGLib动态代理结束
CGLib动态代理开始
我是明星,我来开演唱会
CGLib动态代理结束

2. Spring AOP 的实现

Spring AOP是通过动态代理实现的,上面我们提到的JDK动态代理和CGLIB动态代理,Spring AOP都用到了。

【问题】Spring AOP 什么时候使用JDK,什么时候又使用GCLIB呢?

Spring AOP通过代理工厂创建代理对象,代理工厂中有一个重要的属性:proxyTargetClass,它的值可以为true|false

proxyTargetClass目标对象代理方式
false实现了接口jdk代理
false未实现接口(只有实现类)cglib代理
true实现了接口cglib代理
true未实现接口(只有实现类)cglib代理

该属性是基于程序员配置的,Spring Framework中默认属性值为false,可以通过@EnableAspectJAutoProxy(proxyTargetClass = true)来修改为true。

Spring Boot项目中,@EnableAspectJAutoProxy注解无效,程序员可在application.yml中修改属性值,并且从Spring Boot 2.x 开始,该属性值默认为true,程序员可以在配置文件中将其修改为false

spring:aop:proxy-target-class: false

3. 总结

本篇文章首先介绍了一种新的设计模式 —— 代理模式,然后围绕代理模式介绍了静态代理和动态代理分别是什么。

Spring AOP是基于动态代理实现的,本文着重介绍了两种动态代理方式的差别:JDK动态代理和CGLIB动态代理;并且介绍了Spring AOP什么时候使用JDK代理和CGLIB动态代理。

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

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

相关文章

如何查询下载自然资源相关的法律法规

自然资源部门户网站- 政策法规库 (https://f.mnr.gov.cn/) 以查询下载“节约用水条例”为例&#xff1a;输入标题&#xff0c;点击检索&#xff0c;出现对应的检索结果&#xff1a; 打开详细&#xff0c;可以看到节约用水条例的详细内容&#xff1a; 点击文后的打印或者下载&a…

构建信息安全感知程序(一)

原文&#xff1a;annas-archive.org/md5/cb5e79ad1f7a31eaf76b82b01d6af659 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 前言 公司每年投入数百万美元购买最新的安全产品&#xff0c;从防火墙到门禁系统&#xff0c;但他们未能投资于保护环境中最宝贵的资源——更…

【C++类和对象】const成员函数及流插入提取

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

【论文精读】Attention is all you need

摘要 主要的序列转换模型是基于复杂的循环或卷积神经网络&#xff0c;其中包括一个编码器和一个解码器。性能最好的模型还通过一种注意力机制将编码器和解码器连接起来。我们提出了一种新的简单的网络架构&#xff0c;Transformer&#xff0c;完全基于注意机制&#xff0c;完全…

在ubuntu20.04下迁移anaconda的目录,试验不行后,换成软连接

一、原因 随着不断的搭建不同的算法环境&#xff0c;原本在固态硬盘上安装的anaconda上占用空间越来越多。导致可用的固态硬盘空间越来越少&#xff0c;又因安装的环境太多&#xff0c;重新搭建比较费时费力。有没有直接将当前已经搭建好环境的anaconda 迁移到另外的目录呢&…

【C# 数据结构-队列】

在C#中&#xff0c;队列&#xff08;Queue&#xff09;是一种先进先出&#xff08;First In First Out&#xff0c;FIFO&#xff09;的数据结构&#xff0c;允许添加&#xff08;Enqueue&#xff09;和移除&#xff08;Dequeue&#xff09;元素。队列类在.NET Framework的Syste…

C++参考手册使用说明

C参考手册使用说明 文章目录 C参考手册使用说明1 为什么要使用C参考手册2 网站3 C参考手册离线格式4 C参考手册使用说明1.1 离线C参考手册下载1.2 html离线C参考手册1.3 chm离线C参考手册1.4 linux安装包C参考手册&#xff08;只有英文版本&#xff09;1.5 qch离线C参考手册 更…

接雨水 , 给定二维图,能容多少水

42. 接雨水 - 力扣&#xff08;LeetCode&#xff09; 看着就是非常常规的题目&#xff0c;所以非常有必要掌握。 最少也把O&#xff08;n^2&#xff09;的方法写出来吧。力扣官方题解的三种方法O&#xff08;n&#xff09;都挺好&#xff0c;不过可能有点难读&#xff0c;在此…

【数据结构2-线性表】

数据结构2-线性表 1 线性表-数组2 线性表-单链式结构2.1 前插顺序单链表2.2 后插顺序单链表2.3 循环单链表2.4 双向链表 总结 线性表、栈、队列、串和数组都属于线性结构。 线性结构的基本特点是除第一个元素无直接前驱&#xff0c;最后一个元素无直接后继之外&#xff0c;其他…

12.事件参数

事件参数 事件参数可以获取event对象和通过事件传递数据 获取event对象 <template><button click"addCount">Add</button><p>Count is: {{ count }}</p> </template> <script> export default {data() {return {count:0…

Windows 10 安装配置WSL2(Ubuntu 20.04)教程

Windows 10 安装配置WSL2&#xff08;Ubuntu 20.04&#xff09;教程 一、WSL简介 WSL&#xff08;Windows Subsystem for Linux&#xff09;是一个兼容层&#xff0c;允许在Windows 10上原生运行Linux二进制可执行文件。 二、安装WSL2 3.1 传统手动安装 更新系统&#xff…

lua整合redis

文章目录 lua基础只适合lua连接操作redis1.下载lua依赖2.导包,连接3.常用的命令1.set,get,push命令 2.自增管道命令命令集合4.使用redis操作lua1.实现秒杀功能synchronized关键字 分布式锁 lua 基础只适合 1.编译 -- 编译 luac a.lua -- 运行 lua a.lua2.命名规范 -- 多行注…