14. Spring AOP(二)实现原理

news2024/9/29 9:24:26

源码位置: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{
    @Override
    public void takeAdvertise() {
        System.out.println("我是明星,我来拍广告");
    }
}
  1. 实现代理(代理人,帮明星处理业务中的额外操作)
public class Proxy implements Subject{
    private final RealSubject realSubject;
    public Proxy(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public 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{
    @Override
    public void takeAdvertise() {
        System.out.println("我是明星,我来拍广告");
    }

    @Override
    public void giveConcert() {
        System.out.println("我是明星,我来开演唱会");
    }
}
  1. 实现代理:
public class Proxy implements Subject{
    private final RealSubject realSubject;
    public Proxy(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void takeAdvertise() {
        System.out.println("我是代理人,开始收钱");
        realSubject.takeAdvertise();
        System.out.println("我是代理人,提供售后");
    }

    @Override
    public 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;
    }

    //作用是调用目标方法并对方法进行增强
    @Override
    public 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
     */
    @Override
    public 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.coloradmin.cn/o/1614708.html

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

相关文章

【算法刷题 | 回溯思想 07】4.18(全排列、全排列 ||)

文章目录 11.全排列11.1题目11.2解法&#xff1a;回溯11.2.1回溯思路&#xff08;1&#xff09;函数返回值以及参数&#xff08;2&#xff09;函数返回值&#xff08;3&#xff09;遍历过程 11.2.2代码实现 12.全排列 ||12.1题目12.2解法&#xff1a;回溯12.2.1回溯思路12.2.3代…

教师编制可以跨市调动吗

在教育的广阔天地中&#xff0c;我们常常面临各种职业发展的选择。作为一名教师&#xff0c;是否能够实现跨市调动&#xff0c;这不仅是一个职业发展的问题&#xff0c;更关系到个人生活和职业规划的诸多方面。今天&#xff0c;我们就来探讨一下&#xff0c;拥有编制身份的教师…

2024免费专为Mac用户设计的清理和优化工具CleanMyMac X

CleanMyMac X是一款专为Mac用户设计的清理和优化工具。以下是对CleanMyMac X的详细介绍&#xff1a; 一、主要功能 系统清理&#xff1a;CleanMyMac X能够智能扫描Mac的磁盘空间&#xff0c;识别并清理各种垃圾文件&#xff0c;这些垃圾文件包括重复文件、无用的语言安装包、i…

模拟信号的离散化

本文介绍模拟信号的离散化。 1.采样定理 定义&#xff1a;若想重建输入的模拟信号&#xff0c;采样频率必须大于等于输入模拟信号最高频率的2倍&#xff0c;即&#xff1a; 其中&#xff0c;为采样频率&#xff0c;为输入模拟信号最高频率 否则&#xff0c;信号会发生混叠 2…

攻防世界---misc---easycap

1.下载附件是一个流量包&#xff0c;拿到wireshark中分析 2.查看分级协议 3.过滤data 4.追踪tcp流 5.得到flag

EUV光刻机机密文件被盗 | 百能云芯

4月22日消息&#xff0c;据法国媒体LeMagIT报道&#xff0c;日本光学技术领导厂商 Hoya Corporation&#xff08;豪雅&#xff09;最近遭遇了勒索软件的攻击&#xff0c;其总部和多个业务部门的IT系统遭受波及。据称超过 170 万份内部文件流失&#xff0c;其包括EUV掩模坯料和光…

根据表格该列数据的长度动态变化该列的宽度;

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、代码前言 在使用elementui的表格将数据展示出来时,我们想根据表格该列数据的长度动态变化该列的宽度; 1.看了一下elementui文档有一个 width 的属性,可用它来修改对应列。 2.那么我们需要拿到该列的所有数据去比较…

为何3D动画工作室偏爱使用在线渲染农场?

随着市场需求的不断增长和生产挑战的加剧&#xff0c;3D动画工作室面临着前所未有的压力。为了有效应对这些挑战&#xff0c;众多工作室选择了使用网络渲染农场。这种选择使他们能够借助网络渲染农场的强大渲染能力和高度灵活的资源配置&#xff0c;以此优化他们的工作流程&…

JAVA学习笔记27(异常)

1.异常 ​ *异常(Exception) ​ *快捷键 ctrl alt t 选中try - catch ​ *如果进行了异常处理&#xff0c;那么即使出现了异常&#xff0c;程序可以继续执行 1.1 基本概念 ​ *在Java语言中&#xff0c;将程序执行中发生的不正常情况称为"异常"(开发过程中的语…

Spring Boot 自动装配执行流程

Spring Boot 自动装配执行流程 Spring Boot 自动装配执行流程如下&#xff1a; Spring Boot 启动时会创建一个 SpringApplication实例&#xff0c;该实例存储了应用相关信息&#xff0c;它负责启动并运行应用。实例化 SpringApplication 时&#xff0c;会自动装载META-INF/spr…

HarmonyOS ArkUI滚动类组件-List、ListItem

List 是很常用的滚动类容器组件之一&#xff0c;它按照水平或者竖直方向线性排列子组件&#xff0c; List 的子组件必须是 ListItem &#xff0c;它的宽度默认充满 List 的宽度。 List定义介绍 interface ListInterface {(value?: { initialIndex?: number; space?: numbe…

隐藏表头和最高层级的复选框

隐藏表头和最高层级的复选框 <!-- 表格 --><el-tableref"tableRef"v-loading"tableLoading"default-expand-allclass"flex-1 !h-auto"row-key"regionId":header-cell-class-name"selectionClass":row-class-name&q…

HarmonyOS ArkUI实战开发-窗口模块(Window)

窗口模块用于在同一物理屏幕上&#xff0c;提供多个应用界面显示、交互的机制。 对应用开发者而言&#xff0c;窗口模块提供了界面显示和交互能力。对于终端用户而言&#xff0c;窗口模块提供了控制应用界面的方式。对于操作系统而言&#xff0c;窗口模块提供了不同应用界面的…

深度学习500问——Chapter08:目标检测(3)

文章目录 8.2.7 DetNet 8.2.8 CBNet 8.2.7 DetNet DetNet是发表在ECCV2018的论文&#xff0c;出发点是现有的检测任务backbone都是从分类任务衍生而来的&#xff0c;因此作者想针对检测专用的backbone做一些讨论和研究而设计了DetNet&#xff0c;思路比较新奇。 1. Introduct…

ROS机器人实战,对标古月老师HRMRP机器人(一)——机器人总体方案设计

咳咳&#xff01;这个是自己的毕业设计&#xff0c;内容比较多就拆开发。设计实现了一款SLAM移动机器人&#xff0c;加机械臂完成视觉识别抓取的&#xff0c;同时还有语音识别控制、QT上位机控制、Web网页控制。前几年看古月老师的视频&#xff0c;看到古月老师设计的HRMRP&…

【c++】vector模拟实现与深度剖析

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;c笔记仓 vector涉及到许多细节问题&#xff0c;比如双层深拷贝&#xff0c;迭代器失效等&#xff0c;本篇文章我们通过模拟实现来深度理解这块的内容 目录 1.基本框架2.构造和销毁3.元素访问4.获取…

SpringBoot多数据源(一)

SpringBoot多数据源&#xff08;一&#xff09; 1.多数据源使用场景1.1 业务复杂&#xff08;数据量大&#xff09;1.2 读写分离 2.多数据源配置3.应用4.测试 1.多数据源使用场景 1.1 业务复杂&#xff08;数据量大&#xff09; 简单理解就是业务量复杂&#xff0c;将庞大的数…

基于Springboot的校园新闻网站

基于SpringbootVue的校园新闻网站的设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatis工具&#xff1a;IDEA、Maven、Navicat 系统展示 用户登录 首页展示 校园新闻 论坛交流 留言反馈 后台登录 用户管理 新闻类型管理 校园新闻…

150G全国1米分辨率土地利用数据【2023年】

#1数据摘要 全国1米分辨率土地利用数据 全国范围,分省份分类,1米精度土地利用数据。2023年版本。 数据格式:tif 坐标系:wgs1984 范围:全国各省份 时间:2023年 精度:1米 地类:共计11个地类 中国1m分辨率土地覆盖数据 文件命名与介绍:数据为GeoTIFF栅格格式,每个城市…