Spring AOP:面向切面编程的探索之旅

news2025/3/31 8:59:34

目录

1. AOP

2. Spring AOP 快速入门

2.1 引入 Spring AOP 依赖

2.2 Spring AOP 简单使用

3. Spring AOP 核心概念

3.1 切点

3.1.1 @Pointcut 定义切点

3.1.2 切点表达式

 3.1.2.1 execution 表达式

3.1.2.2 @annotation 表达式

3.2 连接点

3.3 通知(Advice)

3.3.1 通知类型

3.4 切面

3.4.1 @Aspect

3.4.2 切面优先级

3.4.2.1 @Order

4. Spring AOP 原理

4.1 代理模式

4.1.1 静态代理

4.1.2 动态代理

4.2 Spring AOP 面试题

面试题1: Spring AOP 底层代码是怎么写的??

面试题2: Spring AOP 是怎么实现的??

面试题3: jdk 代理和 CGLib 代理的区别??

面试题4: Spring 什么情况下使用 jdk, 什么情况下使用 CGLib??

面试题5: Spring AOP 的实现方式??


1. AOP

AOP(Aspect-Oriented Programming), 是一种思想, 面向切面编程.

其中, 切面, 并非是数学中的 "切面", 而是指某一类具体的事情. 而面向切面, 就是指对某一类事情集中处理(而且是不影响原有业务逻辑的集中处理), 关注的是 "横切关注点"

其实, 我们之前用到的 拦截器, 统一结果返回, 统一异常处理, 都是在集中处理某一类事情, 但又不影响原有代码的正常运行. 这些都是 AOP 思想的体现(只是 AOP 思想, 并非 Spring AOP).

因此, AOP 可以理解为: 在不改动核心业务代码的情况下, 偷偷加功能.(一张无形的网,悄悄地把业务代码拦住, 悄悄地加点料, 然后再放行)

AOP是一种思想, 实现它的方式有很多: SpringAOP, AspectJ, CGLIB, ....等

Spring AOP 只是其中的一种实现方式.

Spring 共有两大核心机制, 一个是 Spring IoC, 一个就是 Spring AOP.

在这句话中,第一个 "Spring" 指的是 Spring Framework,而不是 Spring Boot

原因是,Spring IoC(控制反转)和 Spring AOP(面向切面编程)是 Spring Framework 的两大核心特性。这些特性是 Spring Framework 的基础,Spring Boot 则是在 Spring Framework 的基础上进行封装和简化配置,使得开发者能更快速地构建应用。

  • Spring IoC 主要负责对象的管理和依赖注入。

  • Spring AOP 用于面向切面编程,通过代理机制提供横切关注点(如日志、事务管理等)。

Spring Boot 作为 Spring Framework 的扩展,简化了配置和开发流程,但核心机制(如 IoC 和 AOP)依然是基于 Spring Framework 的。所以当提到 "Spring 的核心机制" 时,通常是指 Spring Framework 中的 IoC 和 AOP。

2. Spring AOP 快速入门

2.1 引入 Spring AOP 依赖

Spring AOP 依赖不能在创建项目时引入, 必须手动引入.

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

2.2 Spring AOP 简单使用

我们使用 Spring AOP 编写一个程序, 记录接口的执行时长.

如上图所示, 通过 Spring AOP, 我们只需在外部通过 Spring AOP 就可以获取到接口的执行时长. 达到了 "获取执行时长" 这一功能和业务代码的解耦.

如果不使用 Spring AOP, 我们就需要在每个接口的起始位置和结束位置获取时间戳, 再计算执行时长, 这样不仅入侵了业务代码, 还需要手动对每个要实现这个功能的接口都编写这些代码.

3. Spring AOP 核心概念

Spring AOP 有以下 4 个核心概念:

  1. 切点
  2. 连接点
  3. 通知
  4. 切面

现实例子
假如你要在所有 Controller 方法执行前打印日志,那么:

  • 切点(Pointcut) = "所有 Controller 里的方法"

  • 连接点(JoinPoint)= "Controller 中某个具体的方法"

  • 通知(Advice) = "方法执行前,打印日志"

  • 切面(Aspect) = "拦截所有 Controller 方法,在执行前打印日志"

3.1 切点

切点(Pointcut)本质上只是一个筛选规则不会影响代码执行, 也不会真正“拦截”任何东西, 它只是告诉 Spring 要对哪些方法进行拦截, 对哪些方法生效.

  • 切点 = @Pointcut 注解(声明切点) + 切点表达式(定义规则)

3.1.1 @Pointcut 定义切点

切点表达式是切点的一部分, 它决定了切点的“筛选规则”.

切点通过切点表达式定义一套规则, 这个规则表名了对哪些方法生效/拦截哪些方法(是一个集合), 描述哪些方法可能成为连接点. 

除了上图在 @Around(通知类型注解) 中声明切点表达式外, 还可以通过 @Pointcut 来声明切点, 并在注解中定义切点表达式.

@Pointcut 注解用于标记一个方法, 表明这个方法将作为一个切点, 并且在注解中定义规则(哪些方法被拦截).

被 @Pointcut 注解标记的方法通常是一个空方法(方法体为), 定义这个方法的目的是为了定义一个可复用的切点名称.

通过 @Pointcut 定义切点后, 这个切点不仅能在本切面中使用, 还可以在其他切面中使用(使用时, 需要写出这个切点的全限定名):

3.1.2 切点表达式

常见的切点表达式有以下两种:

  1. execution: 根据方法的签名来匹配 (如上图所示)
  2. @annotation(......): 根据注解匹配
 3.1.2.1 execution 表达式

execution 是最常用的切点表达式, 用来匹配方法, 语法为:

其中, 访问限定修饰符和异常可以省略.

使用方式如下: 

其中, 通配符 (..) 和 (*) 的含义如下:

  • * : 匹配任意字符, 只匹配一个元素(返回类型, 包, 类名, 方法或者方法参数)
符号匹配含义使用场景及说明
 *匹配任意字符,但只匹配一个元素(返回类型、包名、类名、方法名或者方法参数类型)
a. 包名使用 *表示任意一层包(一层包使用一个 *)
b. 类名使用 *表示任意类
c. 返回值使用 *表示任意返回值类型
d. 方法名使用 *表示任意方法
e. 参数使用 *表示一个任意类型的参数
 ..匹配多个连续的任意符号,可以匹配任意层级的包,或者任意类型的参数,任意个数的参数
a. 使用 .. 配置包名标识此包以及此包下的所有子包
b. 可以使用 .. 配置参数任意个任意类型的参数

示例:

TestController下的 public 修饰, 返回类型为 String 方法名为 t1, 无参方法:

execution(public String com.example.demo.controller.TestController.t1())

省略访问修饰符:

 execution(String com.example.demo.controller.TestController.t1())

匹配所有返回类型:

execution(* com.example.demo.controller.TestController.t1())

匹配 TestController 下的所有无参方法:

execution(* com.example.demo.controller.TestController.*())

匹配 TestController下的所有方法:

execution(* com.example.demo.controller.TestController.*(..))

匹配 controller 包下所有的类的所有方法:

execution(* com.example.demo.controller.*.*(..))

匹配所有包下的 TestController:

execution(* com..TestController.*(..))

匹配 com.example.demo 包下, 子孙包下的所有类的所有方法:

execution(* com.example.demo..*(..))
3.1.2.2 @annotation 表达式

通过 execution 定义切点表达式, Spring AOP 拦截的是符合方法签名规则的方法.

而通过 @annotation 定义切点表达式, Spring AOP 拦截的是标注了特定注解的方法(可以是自定义注解, 也可以是 Spring 提供的注解), 因此更加灵活.

@annotation 中, 需要写出该注解的完全限定名.

如下图所示, 自定义注解 @MyAspect, 并使用 @annotation 将切点规则定义为被 @MyAspect 标记的方法, Spring AOP 将会对这些方法进行拦截并执行通知(Advice):

除了使用自定义的注解标记切点, 也可以使用 Spring 提供的注解定义切点规则.

如下图所示, 使用了 @RequestMapping 的方法, 是连接点(满足切点规则的一个具体方法), 当这些方法执行时, 都会被 AOP 所拦截并进行通知:

3.2 连接点

包含在切点表达式中的某个具体的方法, 在程序执行过程中实际被执行的那个方法, 就是一个连接点(即目标方法).

  • 切点(Pointcut)是一个筛选规则,用来定义哪些方法(连接点)会被 AOP 代理。

  • 连接点(Join Point)是具体的方法,符合切点规则的方法就是连接点。

3.3 通知(Advice)

通知(Advice), 就是 AOP 拦截到目标方法(连接点)后, 具体要做的事/具体要执行的逻辑.

简单来说, 通知就是决定拦截后要做什么事情 .

  • 切点(Pointcut) 只是一个“筛选规则”,它决定哪些方法(连接点)需要被拦截,但它本身不执行任何逻辑.

  • 通知(Advice) 是真正 “干活的人”,它决定拦截后要做什么事情(比如打印日志、权限校验、事务管理等)

3.3.1 通知类型

Spring AOP 提供了 5 种常见的通知,不同的通知类型, 执行的时机不同:

通知类型作用例子
@Before - 前置通知在目标方法执行之前运行进入方法前打印日志
@After - 后置通知在目标方法执行之后运行(无论成功或异常)方法结束后记录操作
@AfterReturning - 返回通知方法成功执行后运行记录方法返回值
@AfterThrowing - 异常通知方法抛异常时运行记录错误日志
@Around - 环绕通知方法执行前和执行后运行计算方法执行时间

使用以上注解, 可以在切面类中将方法标注为通知方法.

对以上 5 种通知类型, 我们可以进行单元测试, 观察结果:

如上图所示, 不同通知类型, 按照其规定的时机, 执行了通知中定义的逻辑.

程序正常运行的情况下, @AfterThrowing 标识的通知方法不会执行, 只有抛出异常时, 该通知方法才会执行. 接下来我们制造异常, 再次观察结果:

 综上, 通知类型被分成了两大类:

  1. 目标方法执行前运行(@Around, @Before)
  2. 目标方法执行后运行(@Around, @After, @AfterReturning, @AfterThrowing)

不难得出, @Around 注解具备了其余注解所有的功能, 因此我们使用 @Around 代替其他注解:

3.4 切面

切面(Aspect) = 切点(Pointcut) + 通知(Advice), 一整套规则+执行逻辑的封装.

简单来说,切面就是“规则 + 行为”,它由切点(Pointcut)和通知(Advice)组成,决定在哪些地方(切点)做什么事(通知).

3.4.1 @Aspect

在 Spring 中, 使用 AOP, 需要用到两个注解:

  1. @Aspect: 将类标记为切面类
  2. @Component: 将这个类的 Bean 交给 Spring 管理, 让 Spring 自动管理这个切面, 以便 Spring 调用类中的通知方法

3.4.2 切面优先级

Spring AOP 允许多个切面作用于同一个目标方法.

当多个切面类, 作用于同一个目标方法(连接点)时, 切面之间是有优先级的:

  • 先执行优先级高的切面中的通知, 后执行优先级低的切面中的通知.

默认的切面优先级是按照名称来排序的:

3.4.2.1 @Order

此外, 可以使用 @Order 注解 来显式指定切面的优先级:

  • 数值越小(负数也可以), 优先级越高(越先执行)

注意: 

  • 对于 JDK 代理. Spring AOP 只对 public 修饰的方法生效,即切点匹配的目标方法必须是 public, 切面的通知才会生效.
  • 对于 CGLib 代理, Spring AOP 对非 private 非 final 修饰的方法生效,即切点匹配的目标方法不能是 private 或者 final 的.
  • SpringBoot 默认使用的是 CGLib 代理. 

综上, 如果要对我们项目中的某个方法进行 AOP 拦截通知, 那么这个方法不能是 private 或者 final 修饰的.

4. Spring AOP 原理

Spring AOP 是通过动态代理来创建代理对象, 从而实现 AOP (面向切面编程).

4.1 代理模式

代理模式, 又称为委托模式. 代理模式是常见设计模式的一种.

代理模式, 通过引入一个中间层(代理对象), 使得客户端在访问真实对象(目标方法)时, 不再是直接对目标方法进行调用, 而是通过代理类间接调用. 并且客户端可以通过代理对象进行额外的控制和操作(不影响真实对象的代码).

代理模式中, 有以下三个核心角色:

  1. 抽象主题(Subject): 是一个接口或抽象类, 它定义了真实对象(被代理对象)和代理对象需要共同实现的方法, 确保代理和被代理对象接口统一(使得用户不需要关心当前使用的是代理对象还是真实对象, 因为它们都遵循相同的接口)
  2. 被代理对象(目标/真实对象, Real Subject): 客户端最终想要访问和操作的对象

  3. 代理对象(Proxy): 对真实对象进行代理, 实现了真实对象的接口(使得客户端看来, 代理和真实对象是相同的). 客户端通过它间接访问目标对象.

综上, 代理模式提供了透明性可替换性.

举个例子: 出租房子 

  1. Subject: 将房东要做的事情, 交给中介来处理. 比如: 给用户展示房子.
  2. Real Subject: 房东
  3. Proxy: 房屋中介

代理模式分为以下两种:

  1. 静态代理: 程序执行前, 代理类的源代码就已经编写完成, 并且在程序编译阶段就已经生成了对应的 .class 文件.
  2. 动态代理: 代理类是在程序运行期间, 由 JVM 根据需要动态地创建出来的, 源代码不需要显式编写.

4.1.1 静态代理

静态代理是指: 在程序执行前, 代理类的源代码就已经编写完成, 并且在程序编译阶段就已经生成了对应的 .class 文件.

举个例子:

一个明星, 他有一个固定的经纪人, 每次有商业活动, 这个经纪人就会帮他事先处理这些事务.

(这个经纪人是这个明星的专属经纪人, 只帮这个明星办事) => 一对一

但是, 人毕竟不是万能的, 这个经纪人也有不擅长的地方, 当经纪人的能力不满足这个明星的需求时, 就有麻烦了.

这就是静态代理.

4.1.2 动态代理

动态代理是指: 代理类是在程序运行期间, 由 JVM 根据需要动态地创建出来的, 源代码通常并不需要显式编写.

还是以明星-经纪人为例.

后来, 这个明星发展的越来越好, 接触的资源越来越多, 因此这个明星签了一个公司, 每次有活动时, 公司都会根据活动的需求, 临时的为这个明星分配合适的经纪人, 这个经纪人就会帮明星处理本次活动相关事务.

这就是动态代理.

Spring AOP 就是通过动态代理来实现的, 其实现方式有以下两种:

  1. JDK 动态代理 (JDK Dynamic Proxy)
  2. CGLIB 代理 (Code Generation Library)

4.2 Spring AOP 面试题

面试题1: Spring AOP 底层代码是怎么写的??

  1. 从 Spring 容器中找到 @Aspect 注解标记的切面类的 Bean (找到程序中的所有切面) 注: @Aspect 是由 Aspect 声明的, 但是具体实现是由 Spring 完成的
  2. 再从这些 Bean 中找 @Around/@Before/... 标记的通知方法, 生成 Advisor(通知类) 的实例, 放到 List 中
  3. 创建代理对象, 并在调用代理对象方法时, 根据通知类型, 在特定的时机执行通知方法中定义的逻辑

面试题2: Spring AOP 是怎么实现的??

Spring AOP 实现动态代理有两种方式:

  1. 通过 jdk 实现动态代理
  2. 通过 CGLib 实现动态代理

面试题3: jdk 代理和 CGLib 代理的区别??

  1. jdk 只能代理接口(目标对象只能是接口)
  2. CGLIb 既可以代理类, 也可以代理接口(目标对象可以是类, 也可以是接口)
  3. "网上有一些文章, 说使用 CGLIb 性能要高于 jdk, 但是这一点我还没有进行验证"

面试题4: Spring 什么情况下使用 jdk, 什么情况下使用 CGLib??

对于 Spring, 其使用哪种进行动态代理, 与目标对象以及代理工厂中的 proxyTargetClass 属性有关.

Spring 是通过工厂模式来创建代理的, 并且根据的代理工厂中的 proxyTargetClass  属性值进行创建:

proxyTargetClass目标对象代理方式
false实现了接口jdk代理
false未实现接口(只是单独一个类)cglib代理
true实现了接口cglib代理
true未实现接口(只是单独一个类)cglib代理

由于 SpringBoot 是基于 Spring Framework 进行封装的, 因此大多数情况下, SpringBoot 和Spring Framework是保持一致的.

但是 Spring AOP 这里是个例外:

  1. SpringFramework 的 proxytargetclass 默认值为 false
  2. Springboot 2.x 默认值为false
  3. Springboot 3.x 默认值为true

因此, 我们现在的 SpringBoot 项目, 默认使用 CGLib 动态代理实现 AOP.

面试题5: Spring AOP 的实现方式??

面试官:谈谈你对IOC和AOP的理解及AOP四种实现方式[通俗易懂]-腾讯云开发者社区-腾讯云


END

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

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

相关文章

使用QT画带有透明效果的图

分辨率&#xff1a;24X24 最大圆 代码: #include <QApplication> #include <QImage> #include <QPainter>int main(int argc, char *argv[]) {QImage image(QSize(24,24),QImage::Format_ARGB32);image.fill(QColor(0,0,0,0));QPainter paint(&image);…

RocketMQ可视化工具使用 - Dashboard(保姆级教程)

1、github拉取代码&#xff0c;地址&#xff1a; https://github.com/apache/rocketmq-dashboard 2、指定Program arguments&#xff0c;本地启动工程 勾上这个Program arguments&#xff0c;会出现多一个对应的框 写入参数 --server.port1280 --rocketmq.config.namesrvAddr…

用Unity实现UDP客户端同步通信

制作UDPNetMgr网络管理模块 这段代码定义了一个名为UDPNetMgr的 Unity 脚本类&#xff0c;用于管理 UDP 网络通信&#xff0c;它作为单例存在&#xff0c;在Awake方法中创建收发消息的线程&#xff0c;Update方法处理接收到的消息&#xff1b;StartClient方法启动客户端连接&a…

pandoc安装及基础使用

pandoc安装 访问pandoc tags,切换至想要安装的版本&#xff0c;本次安装3.6.4 下载windows版本 下载texlive镜像&#xff0c;将文件转换成pdf需要用到 点开后会进入最近的镜像网站 下载完成后解压iso文件&#xff0c;以管理员身份运行install-tl-windows.bat&#xff…

3.27学习总结 算法题

自己用c语言做的&#xff0c;不尽如意 后面看了题解&#xff0c;用的是c&#xff0c;其中string 变量和字符串拼接感觉比c方便好多&#xff0c;可以用更少的代码实现更好的效果&#xff0c;打算之后去学习c&#xff0c;用c写算法。 递归&#xff0c;不断输入字符&#xff0c;…

案例分享|树莓派媒体播放器,重构商场广告的“黄金三秒”

研究显示&#xff0c;与传统户外广告相比&#xff0c;数字户外广告在消费者心中的记忆率提高了17%&#xff0c;而动态户外广告更是能提升16%的销售业绩&#xff0c;整体广告效率提升了17%。这一显著优势&#xff0c;使得越来越多资源和技术流入数字广告行业。 户外裸眼3D广告 无…

Redisson - 分布式锁和同步器

文章目录 锁&#xff08;Lock&#xff09;公平锁&#xff08;Fair Lock&#xff09;联锁&#xff08;MultiLock&#xff09;红锁&#xff08;RedLock&#xff09; 【已废弃】读写锁&#xff08;ReadWriteLock&#xff09;信号量&#xff08;Semaphore&#xff09;可过期许可信号…

Zustand 状态管理:从入门到实践

Zustand 状态管理&#xff1a;从入门到实践 Zustand 是一个轻量、快速且灵活的 React 状态管理库。它基于 Hooks API&#xff0c;提供了简洁的接口来创建和使用状态&#xff0c;同时易于扩展和优化。本文将通过一个 TODO 应用实例带你快速入门 Zustand&#xff0c;并探讨其核心…

PGP实现简单加密教程

模拟情景&#xff1a; 假设001和002两位同学的电脑上都安装了PGP&#xff0c;现在两人需要进行加密通讯。 一、创建密钥 1.新建密钥&#xff0c;输入名称和邮箱&#xff0c;输入8位口令&#xff0c;根据指示完成。 2.将其添加到主密钥&#xff0c;鼠标右击出现选项。 这里出…

7.8 窗体间传递数据

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的 当项目中有多个窗体时&#xff08;在本节中为两个窗体&#xff1a;Form1和Form2&#xff09;&#xff0c;窗体间传递数据有以下几种方…

【redis】集群 数据分片算法:哈希求余、一致性哈希、哈希槽分区算法

文章目录 什么是集群数据分片算法哈希求余分片搬运 一致性哈希扩容 哈希槽分区算法扩容相关问题 什么是集群 广义的集群&#xff0c;只要你是多个机器&#xff0c;构成了分布式系统&#xff0c;都可以称为是一个“集群” 前面的“主从结构”和“哨兵模式”可以称为是“广义的…

基于Springboot的网上订餐系统 【源码】+【PPT】+【开题报告】+【论文】

网上订餐系统是一个基于Java语言和Spring Boot框架开发的Web应用&#xff0c;旨在为用户和管理员提供一个便捷的订餐平台。该系统通过简化餐饮订购和管理流程&#xff0c;为用户提供快速、高效的在线订餐体验&#xff0c;同时也为管理员提供完善的后台管理功能&#xff0c;帮助…

【redis】集群 如何搭建集群详解

文章目录 集群搭建1. 创建目录和配置2. 编写 docker-compose.yml完整配置文件 3. 启动容器4. 构建集群超时 集群搭建 基于 docker 在我们云服务器上搭建出一个 redis 集群出来 当前节点&#xff0c;主要是因为我们只有一个云服务器&#xff0c;搞分布式系统&#xff0c;就比较…

飞牛NAS本地部署小雅Alist结合内网穿透实现跨地域远程在线访问观影

文章目录 前言1. VMware安装飞牛云&#xff08;fnOS&#xff09;1.1 打开VMware创建虚拟机1.3 初始化系统 2. 飞牛云搭建小雅Alist3. 公网远程访问小雅Alist3.1 安装Cpolar内网穿透3.2 创建远程连接公网地址 4. 固定Alist小雅公网地址 前言 嘿&#xff0c;小伙伴们&#xff0c…

Linux版本控制器Git【Ubuntu系统】

文章目录 **前言**一、版本控制器二、Git 简史三、安装 Git四、 在 Gitee/Github 创建项目五、三板斧1、git add 命令2、git commit 命令3、git push 命令 六、其他1、git pull 命令2、git log 命令3、git reflog 命令4、git stash 命令 七、.ignore 文件1、为什么使用 .gitign…

browser-use 库网页元素点击测试工具

目录 代码代码解释输出结果 代码 import asyncio import jsonfrom browser_use.browser.browser import Browser, BrowserConfig from browser_use.dom.views import DOMBaseNode, DOMElementNode, DOMTextNode from browser_use.utils import time_execution_syncclass Eleme…

解决GitLab无法拉取项目

1、验证 SSH 密钥是否已生成 ls ~/.ssh/ 如果看到类似 id_rsa 和 id_rsa.pub 的文件&#xff0c;则说明已存在 SSH 密钥。 避免麻烦&#xff0c;铲掉重来最方便。 如果没有&#xff0c;请生成新的 SSH 密钥&#xff1a; ssh-keygen -t rsa -b 4096 -C "your_emailexam…

FPGA学习篇——Verilog学习之寄存器的实现

1 寄存器理论 这里在常见的寄存器种加了一个复位信号sys_rst_n。&#xff08;_n后缀表示复位信号低电平有效&#xff0c;无这个后缀的则表示高电平有效&#xff09; 这里规定在时钟的上升沿有效&#xff0c;只有当时钟的上升沿来临时&#xff0c;输出out 才会改变&#xff0c;…

【VUE】ant design vue实现表格table上下拖拽排序

适合版本&#xff1a;ant design vue 1.7.8 实现效果&#xff1a; 代码&#xff1a; <template><div class"table-container"><a-table:columns"columns":dataSource"tableData":rowKey"record > record.id":row…

Vue实现动态数据透视表(交叉表)

需求:需要根据前端选择的横维度、竖维度、值去生成一个动态的表格&#xff0c;然后把交叉的值放入到对应的横维度和竖维度之下&#xff0c;其实就是excel里面的数据透视表功能&#xff0c;查询交叉语句为sql语句。 实现页面&#xff1a; 选择一下横维度、竖维度、值之后点击查…