使用Spring的AOP

使用Spring的AOP

  • 一、AOP 的常用注解
    • 1.切面类@Aspect
    • 2.@Pointcut
    • 3.前置通知@Before
    • 4.后置通知@AfterReturning
    • 5.环绕通知@Around
    • 6.异常通知@AfterThrowing
    • 7.最终通知@After
    • 8.切面顺序@Order
    • 9.启用自动代理@EnableAspectJAutoProxy
  • 二、AOP注解方式开发
  • 三、AOP 全注解开发
  • 四、基于XML配置方式的AOP(了解)


  • Spring 对 AOP 的实现包括以下3种方式:
    • 第一种方式:Spring框架结合AspectJ框架实现的AOP,基于注解方式。
    • 第二种方式:Spring框架结合AspectJ框架实现的AOP,基于XML方式。
    • 第三种方式:Spring框架自己实现的AOP,基于XML方式。
  • 实际开发种都是Spring + AspectJ来实现的AOP。
  • 什么是AspectJ?(Eclipse组织的一个支持AOP的框架。AspectJ框架是独立于Spring框架之外的一个框架,Spring框架用了AspectJ)
  • AspectJ项目起源于帕洛阿尔托(Palo Alto)研究中心(缩写为PARC)。该中心由Xerox集团资助,Gregor Kiczales领导,从1997年开始致力于AspectJ的开发,1998年第一次发布给外部用户,2001年发布1.0 release。为了推动AspectJ技术和社团的发展,PARC在2003年3月正式将AspectJ项目移交给了Eclipse组织,因为AspectJ的发展和受关注程度大大超出了PARC的预期,他们已经无力继续维持它的发展。

一、AOP 的常用注解

1.切面类@Aspect

  • @Aspect作用是把当前类标识为一个切面供容器读取。

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE})
    public @interface Aspect {
        String value() default "";
    }
    

2.@Pointcut

  • @Pointcut注解标注在方法上面,用来定义切入点。
  • 可以这样做:将切点表达式单独的定义出来,在需要的位置引入即可。
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface Pointcut {
        String value() default "";
        String argNames() default "";
    }
    

3.前置通知@Before

  • @Before目标方法执行之前的通知
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface Before {
        String value();
        String argNames() default "";
    }
    

4.后置通知@AfterReturning

  • @AfterReturning目标方法执行之后的通知
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface AfterReturning {
        String value() default "";
        String pointcut() default "";
        String returning() default "";
        String argNames() default "";
    }
    

5.环绕通知@Around

  • @Around目标方法之前添加通知,同时目标方法执行之后添加通知。
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface Around {
        String value();
        String argNames() default "";
    }
    

6.异常通知@AfterThrowing

  • @AfterThrowing发生异常之后执行的通知
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface AfterThrowing {
        String value() default "";
        String pointcut() default "";
        String throwing() default "";
        String argNames() default "";
    }
    

7.最终通知@After

  • @After放在finally语句块中的通知
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface After {
        String value();
    
        String argNames() default "";
    }
    

8.切面顺序@Order

  • 我们知道,业务流程当中不一定只有一个切面,可能有的切面控制事务,有的记录日志,有的进行安全控制,如果多个切面的话,顺序如何控制:可以使用@Order注解来标识切面类,为@Order注解的value指定一个整数型的数字,数字越小,优先级越高。
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
    @Documented
    public @interface Order {
    	/**
    	 * The order value.
    	 * <p>Default is {@link Ordered#LOWEST_PRECEDENCE}.
    	 * @see Ordered#getOrder()
    	 */
    	int value() default Ordered.LOWEST_PRECEDENCE;
    }
    

9.启用自动代理@EnableAspectJAutoProxy

  • 开启自动代理之后,凡事带有@Aspect注解的bean都会生成代理对象。
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(AspectJAutoProxyRegistrar.class)
    public @interface EnableAspectJAutoProxy {
    	boolean proxyTargetClass() default false;
    	boolean exposeProxy() default false;
    }
    

二、AOP注解方式开发

  • 注意:本文使用了 log4j2 日志,如果不知道可以看我的博客 ===> Spring对IoC的实现中的第一个Spring程序

  • 注意本文也使用了 junit 进行单元测试。

  • 使用Spring+AspectJAOP需要引入的依赖如下:

    <!--spring的核心依赖 aop core beans jcl expression 等-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.1.4</version>
    </dependency>
    <!-- AOP 依赖的AspectJ -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>6.1.4</version>
    </dependency>
    
  • Spring配置文件中添加context命名空间和aop命名空间

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                               http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    
    </beans>
    
  • 第一步:定义目标类以及目标方法

    package com.gdb.service;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;
    
    //目标类
    @Service
    public class OrderService {
        private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
        //目标方法
        public void detail() {
            logger.info("正在打印订单详情......");
        }
    }
    
  • 第二步:编写切面类

    package com.gdb.aspect;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    //切面类(通知+切点 = 切面)
    @Aspect
    @Component
    public class MyAspect {
        private static final Logger logger = LoggerFactory.getLogger(MyAspect.class);
    
    	//这是需要增强的代码(通知)
        @Before("execution(* com.gdb.service..* (..))") // com.gdb.service包下的所有方法
        public void beforeAdvice() {
            logger.info("前置通知执行了");
        }
    
        @AfterReturning("execution(* com.gdb.service..* (..))") // com.gdb.service包下的所有方法,
        public void afterReturningAdvice() {
            logger.info("后置通知执行了");
        }
    
        @Around("execution(* com.gdb.service..* (..))") // com.gdb.service包下的所有方法,
        public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            logger.info("前置环绕通知执行了");
            proceedingJoinPoint.proceed(); // 执行目标方法
            logger.info("后置环绕通知执行了");
        }
    
        @AfterThrowing("execution(* com.gdb.service..* (..))") // com.gdb.service包下的所有方法,
        public void afterThrowingAdvice() {
            logger.info("异常通知执行了");
        }
    
        @After("execution(* com.gdb.service..* (..))") // com.gdb.service包下的所有方法,
        public void afterAdvice() {
            logger.info("最终通知执行了");
        }
    }
    
  • 第三步:在配置文件中启动包扫描启用自动代理

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                               http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
        <!--开启组件扫描-->
        <context:component-scan base-package="com.gdb"/>
        <!--开启自动代理-->
        <aop:aspectj-autoproxy proxy-target-class="false"/>
        <!--
            <aop:aspectj-autoproxy  proxy-target-class="true"/> 开启自动代理之后,凡事带有@Aspect注解的bean都会生成代理对象。
            proxy-target-class="true" 表示采用cglib动态代理。
            proxy-target-class="false" 表示采用jdk动态代理。默认值是false。即使写成false,当没有接口的时候,也会自动选择cglib生成代理类。
        -->
    </beans>
    
  • 第四步:编写测试程序

    @Test
    public void test(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
        orderService.detail();
    }
    
  • 第五步:执行结果
    在这里插入图片描述

    • 通过上面的执行结果就可以判断他们的执行顺序了,这里不再赘述。
  • 第六步:结果中没有异常通知,这是因为目标程序执行过程中没有发生异常。我们尝试让目标方法发生异常:

    package com.gdb.service;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;
    
    @Service
    public class OrderService {
        private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
    
        public void detail() {
            logger.info("正在打印订单详情......");
            throw new RuntimeException();
        }
    }
    
  • 第七步:执行结果:
    在这里插入图片描述

    • 通过测试得知,当发生异常之后,最终通知也会执行,因为最终通知@After会出现在finally语句块中。出现异常之后,后置通知环绕通知的结束部分不会执行。
  • 优化使用切点表达式:

    • 上面编写的切面类的缺点是:
      • 第一:切点表达式重复写了多次,没有得到复用。
      • 第二:如果要修改切点表达式,需要修改多处,难维护。
  • 可以这样做:将切点表达式单独的定义出来,在需要的位置引入即可。

    package com.gdb.aspect;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    public class MyAspect {
        private static final Logger logger = LoggerFactory.getLogger(MyAspect.class);
    
        @Pointcut("execution(* com.gdb.service..* (..))")
        public void pointcut() {
        }
    
        @Before("pointcut()") // com.gdb.service包下的所有方法
        public void beforeAdvice() {
            logger.info("前置通知执行了");
        }
    
        @AfterReturning("pointcut()") // com.gdb.service包下的所有方法,
        public void afterReturningAdvice() {
            logger.info("后置通知执行了");
        }
    
        @Around("pointcut()") // com.gdb.service包下的所有方法,
        public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            logger.info("前置环绕通知执行了");
            proceedingJoinPoint.proceed();
            logger.info("后置环绕通知执行了");
        }
    
        @AfterThrowing("pointcut()") // com.gdb.service包下的所有方法,
        public void afterThrowingAdvice() {
            logger.info("异常通知执行了");
        }
    
        @After("pointcut()") // com.gdb.service包下的所有方法,
        public void afterAdvice() {
            logger.info("最终通知执行了");
        }
    }
    
  • 使用@Pointcut注解来定义独立的切点表达式。

  • 注意这个@Pointcut注解标注的方法随意,只是起到一个能够让@Pointcut注解编写的位置。


三、AOP 全注解开发

  • 第一步:就是编写一个类,在这个类上面使用大量注解来代替spring的配置文件,spring配置文件消失了,如下:
    package com.gdb.config;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    @Configuration // 配置类
    @ComponentScan("com.gdb")
    @EnableAspectJAutoProxy(proxyTargetClass = false)
    public class SpringConfig {
    }
    
  • 第二步:测试程序也变化了
    @Test
    public void test() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
        orderService.detail();
    }
    
  • 第三步:执行结果
    在这里插入图片描述

四、基于XML配置方式的AOP(了解)

  • 第一步:编写目标类
package com.gdb.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class OrderService {
    private static final Logger logger = LoggerFactory.getLogger(OrderService.class);

    public void detail() {
        logger.info("正在打印订单详情......");
    }
}
  • 第二步:编写切面类,并且编写通知
package com.gdb.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyAspect {
    private static final Logger logger = LoggerFactory.getLogger(MyAspect.class);

    public void beforeAdvice() {
        logger.info("前置通知执行了");
    }

    public void afterReturningAdvice() {
        logger.info("后置通知执行了");
    }

    public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        logger.info("前置环绕通知执行了");
        proceedingJoinPoint.proceed();
        logger.info("后置环绕通知执行了");
    }

    public void afterThrowingAdvice() {
        logger.info("异常通知执行了");
    }

    public void afterAdvice() {
        logger.info("最终通知执行了");
    }
}
  • 第三步:编写spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
	                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
	                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="myAspect" class="com.gdb.aspect.MyAspect"/>
    <bean id="orderService" class="com.gdb.service.OrderService"/>

    <!--aop配置-->
    <aop:config>
        <!--切点表达式-->
        <aop:pointcut id="p" expression="execution(* com.gdb.service..* (..))"/>
        <!--切面-->
        <aop:aspect ref="myAspect">
            <!--切面=通知 + 切点-->
            <aop:before method="beforeAdvice" pointcut-ref="p"/>
            <aop:after-returning method="afterReturningAdvice" pointcut-ref="p"/>
            <aop:around method="aroundAdvice" pointcut-ref="p"/>
            <aop:after-throwing method="afterThrowingAdvice" pointcut-ref="p"/>
            <aop:after method="afterAdvice" pointcut-ref="p"/>
        </aop:aspect>
    </aop:config>
</beans>
  • 第四步:编写测试程序
@Test
public void test() {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
    orderService.detail();
}
  • 第四步:执行结果
    在这里插入图片描述
  • 通过结果可以看出来顺序和前面的不一样了,我感觉是配置文件中的顺序有关系,由于主要都是使用注解的方式。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java中使用rabbitmq

文章目录 前言一、引入和配置1.引入2.配置 二、使用1.队列2.发布/订阅2.1 fanout(广播)2.2 direct(Routing/路由)2.3 Topics(主题)2.4 Headers 总结 前言 mq常用于业务解耦、流量削峰和异步通信,rabbitmq是使用范围较广,比较稳定的一款开源产品,接下来我们使用springboot的sta…

资料下载-嵌入式 Linux 入门

学习的第一步是去下载资料。 1. 有哪些资料 所有资料分 4 类&#xff1a; ① 开发板配套资料(原理图、虚拟机的映像文件、烧写工具等)&#xff0c;放在百度网盘 ② 录制视频过程中编写的文档、源码、图片&#xff0c;放在 GIT 仓库 ③ u-boot、linux 内核、buildroot 等比较大…

机器学习评价指标(分类、目标检测)

https://zhuanlan.zhihu.com/p/364253497https://zhuanlan.zhihu.com/p/46714763https://blog.csdn.net/u013250861/article/details/123029585 1.1 混淆矩阵 在介绍评价指标之前&#xff0c;我们首先要介绍一下混淆矩阵&#xff08;confusion matrix&#xff09;。混淆矩阵…

C++的类与对象(五):赋值运算符重载与日期类的实现

目录 比较两个日期对象 运算符重载 赋值运算符重载 连续赋值 日期类的实现 Date.h文件 Date.cpp文件 Test.cpp文件 const成员 取地址及const取地址操作符重载 比较两个日期对象 问题描述&#xff1a;内置类型可直接用运算符比较&#xff0c;自定义类型的对象是多个…

《日期类》的模拟实现

目录 前言&#xff1a; 头文件类与函数的定义Date.h 实现函数的Date.cpp 测试Test.cpp 运行结果&#xff1a; 前言&#xff1a; 我们在前面的两章初步学习认识了《类与对象》的概念&#xff0c;接下来我们将实现一个日期类&#xff0c;是我们的知识储备更加牢固。 头文件…

角蜥优化算法 (Horned Lizard Optimization Algorithm ,HLOA)求解无人机路径优化

一、无人机路径规划模型介绍 无人机三维路径规划是指在三维空间中为无人机规划一条合理的飞行路径,使其能够安全、高效地完成任务。路径规划是无人机自主飞行的关键技术之一,它可以通过算法和模型来确定无人机的航迹,以避开障碍物、优化飞行时间和节省能量消耗。 二、算法介…

【JAVA】CSS3:3D、过渡、动画、布局、伸缩盒

1 3D变换 1.1 3D空间与景深 /* 开启3D空间,父元素必须开启 */transform-style: preserve-3d;/* 设置景深&#xff08;你与z0平面的距离 */perspective:50px; 1.2 透视点位置 透视点位置&#xff1a;观察者位置 /* 100px越大&#xff0c;越感觉自己边向右走并看&#xff0c;…

K8S之实现业务的蓝绿部署

如何实现蓝绿部署 什么是蓝绿部署&#xff1f;蓝绿部署的优势和缺点优点缺点 通过k8s实现线上业务的蓝绿部署 什么是蓝绿部署&#xff1f; 部署两套系统&#xff1a;一套是正在提供服务系统&#xff0c;标记为 “绿色” &#xff1b;另一套是准备发布的系统&#xff0c;标记为…

LInux系统架构----Apache与Nginx动静分离

LInux系统架构----Apache与Nginx动静分离 一.动静分离概述 Nginx的静态处理能力比较强&#xff0c;但是动态处理能力不足&#xff0c;因此在企业中常采用动静分离技术在LNMP架构中&#xff0c;静态页面交给Nginx处理&#xff0c;动态页面交给PHP-FPM模块处理。在动静分离技术…

【软件测试面试】银行项目测试面试题+答案(二)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 面试题&#xff1…

HTTP/2的三大改进:头部压缩、多路复用和服务器推送

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

CSS 居中对齐 (水平居中 )

水平居中 1.文本居中对齐 内联元素&#xff08;给容器添加样式&#xff09; 限制条件&#xff1a;仅用于内联元素 display:inline 和 display: inline-block; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><…

[c++] 查表 —— 策略模式和职责链模式的核心

查表法在工厂模式、策略模式以及职责链模式中都有使用。以工厂模式为例&#xff0c;表中存储的数据&#xff0c;key 是商品的类型&#xff0c;value 是生产这个商品的工厂。在生产商品的时候&#xff0c;直接根据商品类型从表中获得商品对应的工厂&#xff0c;然后通过工厂生产…

多维时序 | Matlab实现BiTCN双向时间卷积神经网络多变量时间序列预测

多维时序 | Matlab实现BiTCN双向时间卷积神经网络多变量时间序列预测 目录 多维时序 | Matlab实现BiTCN双向时间卷积神经网络多变量时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现BiTCN双向时间卷积神经网络多变量时间序列预测&#xff08;完整…

HTML概念

文章目录 1. HTML 概念1.1. 简介1.2. 思想1.3. 特点1.4. 语法1.4.1. 标签1.4.2. 属性1.4.3. 标签体1.4.4. 注释 2. HTML 实体2.1. 练习 3. HTML 结构3.1. <!DOCTYPE html>声明3.2. html根标签 4. 补充4.1. 管理文件4.2. 配置 VsCode4.2. 配置 VsCode 1. HTML 概念 1.1. 简…

QT画图功能

QT画图功能 每个QWidget都自带的功能&#xff0c;继承了QPainteDevice都可以使用QPainter来进行绘图。 画图需要调用paintEvent绘制事件&#xff0c;paintEvent事件时QWidget类自带的事件。 重写paintEvent事件。&#xff08;重写事件&#xff1a;如果父类有某个方法&#xff…

路由器动态路由配置

本博客为观看湖科大的教书匠系列计算机网络视频的学习笔记。 静态路由选择动态路由选择采用人工配置的方式给路由器添加网络路由、默认路由和特定主机路由等路由条目。路由器通过路由选择协议自动获取路由信息。静态路由选择简单、开销小&#xff0c;但不能及时适应网络状态(流…

【SOFARPC】SOFA入门实战

背景 由于最近交付项目&#xff0c;甲方使用了SOFA这套体系&#xff0c;之前虽然有过一些了解&#xff0c;但是真正实战还是没有那么熟悉&#xff0c;因此搭建一个实战的demo&#xff0c;熟悉一下相关内容。 SOFA SIMALE DEMO 项目搭建 项目目录结构 如上图所示&#xff0…

基于电鳗觅食优化算法(Electric eel foraging optimization,EEFO)的无人机三维路径规划(提供MATLAB代码)

一、无人机路径规划模型介绍 无人机三维路径规划是指在三维空间中为无人机规划一条合理的飞行路径&#xff0c;使其能够安全、高效地完成任务。路径规划是无人机自主飞行的关键技术之一&#xff0c;它可以通过算法和模型来确定无人机的航迹&#xff0c;以避开障碍物、优化飞行…

数据结构小记【Python/C++版】——BST树篇

一&#xff0c;基础概念 BST树&#xff0c;英文全称:Binary Search Tree&#xff0c;被称为二叉查找树或二叉搜索树。 如果一个二叉查找树非空&#xff0c;那么它具有如下性质&#xff1a; 1.左子树上所有节点的值小于根节点的值&#xff0c;节点上的值沿着边的方向递减。 2…