FastJson反序列化学习-01

🌸 FastJson

FastJson是一个由阿里巴巴开发的高性能JSON处理库,支持Java对象与JSON字符串之间的互相转换。

本次漏洞研究基于FastJson1.2.24版本。也就是最早出现FastJson反序列化漏洞的版本。

CVE-2017-18349,FastJson<=1.2.24

🍂 Demo

先来熟悉一下什么是Json

package org.y4y17;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class Main {
    public static void main(String[] args) {
        String s = "{\"name\":\"Y4y17\",\"age\":17}";

        JSONObject jsonObject = JSON.parseObject(s);
        System.out.println(jsonObject);
        System.out.println(jsonObject.get("name"));
        System.out.println(jsonObject.get("age"));
    }
}

这里简单的写了一个DemoString s = "{\"name\":\"Y4y17\",\"age\":17}";创建了一个字符串,然后利用JSON.parseObject方法来将字符串解析为对象。

  • JSON.parseObject,是将Json字符串转化为相应的对象;
  • JSON.toJSONString,是将对象转化为Json字符串。

然而在反序列化的时候,可以指定转化的对象类型!此时JSON.parseObject方法便会将其转化为对应的一个javaBean,比如我们这里存在一个JavaBean

package org.y4y17;

public class Person {
    private String name;
    private int age;
    public Person() {
        System.out.println("调用了constructor方法");
    }
    public String getName() {
        System.out.println("调用了getName方法");
        return name;
    }

    public void setName(String name) {
        this.name = name;
        System.out.println("调用了setName方法");
    }

    public int getAge() {
        System.out.println("调用了getAge方法");
        return age;
    }

    public void setAge(int age) {
        System.out.println("调用了setAge方法");
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

那么在转化为Java对象的时候可以通过指定要转化的类,来完成对应对象的转化。如下代码:

package org.y4y17;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class Main {
    public static void main(String[] args) {
        String s = "{\"name\":\"Y4y17\",\"age\":17}";

        Person person = JSON.parseObject(s, Person.class);
        System.out.println(person.getAge());
    }
}

在上面的Person类中的各个setget方法中,打印了相关的方法名,以便更加清晰的看到调用关系。如上代码的执行结果如下:

当我们指定了要转化的类的时候,发现整个转化的过程中,先调用了构造器,然后就是调用相关的set方法和get方法(在这里的get方法是在getAge()调用的时候触发的)。

然而在FastJson反序列化的时候,可以指定一个@type字段,用来表明指定反序列化的目标恶意对象类。比如我们在String字符串里面添加一个@type字段。

String s = "{\"@type\":\"org.y4y17.Person\",\"name\":\"Y4y17\",\"age\":17}"

package org.y4y17;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class Main {
    public static void main(String[] args) {
        String s = "{\"@type\":\"org.y4y17.Person\",\"name\":\"Y4y17\",\"age\":17}";
        JSONObject jsonObject = JSON.parseObject(s);
        System.out.println(jsonObject);
    }
}

然而在上面的JSON转化为Java对象的时候,通过写入@type字段,实现了指定类的反序列化,成功的调用了Person类中的setget方法以及构造器。

🍂 流程分析

下断点进行调试:

跟进到JSONparseObject方法中:在JSON类中可以看到存在很多种方法,其中他们的参数是不同的:

parseObject方法中:

首先parse主要负责解析我们传递的text,最后便会返回这个Person类对象,在这个过程中就会调用构造器和set方法,而最后的return,将对象强制转化为JSONObject对象,这个过程中会调用到Person类中的get方法!继续跟进到parse方法中:

其中这个parse方法也是一个静态的方法,可以直接在外部进行调用:

在上述的代码中,会创建一个默认的JSON解析器:

DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance(), features);
Object value = parser.parse();

然后利用创建的解析器进行解析,最终返回Person类对象。ParserConfig.getGlobalInstance()是创建了一个解析器,进行初始化,主要就是初始化一些配置,可以跟进一下看:

可以看到这个parserConfig类中的构造器:

放进去了一下默认的反序列化器;比如:derializers.put(Map.class, MapDeserializer.instance);如果是Map类型的话,就使用MapDeserializer反序列化器

同时整个过程中还设置了一些黑名单,也就是当时在这个漏洞出现之前,禁止指定的类:

如这里的java.lang.Thread,线程类。

接下来JSON扫描器,就会从头开始扫描我们传递进来的string

首先就会判断第一个字符是不是一个" { ",如果是的话,也就是代表着一个JSON格式的字符串,进入到if条件里面:

或者是不是一个“[”,如果是的话,其实就是一个数组。由于我们传递的就是一个符合JSON格式的字符串,所以if直接就进入了,其他的都不会进去,此时就会创建好默认的JSON解析器了,下一步就是解析的过程:

跟进到这个parse方法中:

parse方法中通过switch来匹配第一个字符到底是什么,因为我们是一个左大括号,所以继续往下走:

这里便会创建一个JSONObject,他其实是一个Map

这里相当于是创建了一个新的Mapsize0

这里我们继续跟进到parseObject方法中!先是经过一系列的判断:

因为这里我们是“{”,所以这里所有的if条件都不满足,直接过:

接下来经过了一个死循环!必须存在break 或者return才能结束。ifwhile主要是进行了重复的事情,寻找“,”,如果存在的话,就跳过。

然后继续判断是不是双引号和:、}等符号:整个过程中就是取出来了一个key!也就是@type

继续往下走的话,其实会去判断拿到这个key是不是和默认的key一样!默认的可以就是@type

之后就会通过loadClass进行类加载了!继续跟进:

先从缓存中查找是不是已经加载过了,或者有相关的记录(空间换时间),如果存在的话,就直接拿出来用了!

之后便会判断类名的首字符是不是“[”,也就代表着数组,之后又判断是不是以“L”开头,同时以“;”结尾。我们这里都不满足,所以就不管了,继续往下走:

这里就会获取到AppClassLoader,通过AppClassLoader进行加载!然后就会把他放到缓存中,最后return 这个类。此时整个loaderclass也就结束了。

接下来回到了默认的JSON解析器中:

这里if条件都不满足,继续往下走吧:

这里的object就是最开始创建的JSONObject,当时说他是一个Map,整个过程中还没有往里面存放东西,所以他的size就是0,这里的if条件还是不成立,继续往下走就会发现:ObjectDeserializer deserializer = config.getDeserializer(clazz);

config里面获取了一个反序列化器!将我们的类放到里面。而这个获取到的反序列化器就是前面我们看到ParserConfig构造器里面初始化的那些,当然我们这里继续跟进,看看他获取到的反序列化器是什么?

这里从构造器初始化的那些反序列化器中获取对应的,其实是没有的!继续往下走是泛型 然后调用了:getDeserializer

跟进之后,发现还是一直在找这个反序列化器,一直找不到,就各种判断是不是type为空等,是不是自己通过注解的方式写了一个反序列化器,显然我们是没有的!所以这里的if都是进不去的!

之后就开始判断是不是黑名单里面的~ 继续往下走:

然后又去判断了是不是Enum等,到最后一直找不到,就创建了一个JavaBeanDeserializer反序列化器!继续跟进到这个方法中:

这里存在一个变量asmEnable变量,初始化是true

继续走,调用了JavaBeaninfo类中的getBuilderClass方法,由于传递的jsonType是一个null值,最终方法return null。还是继续往下走,superclass复制本身,通过一个死循环,获取这个类的修饰符,判断是不是public。如果不是的话,那么asmEnable就会被设置为false

整个过程中又去判断了是不是参数为空,指定的类是不是一个接口!(反正又是各种不满足!)

最后调用了一个JavaBeaninfobuild方法,跟进看一下:

到这个build方法中,发现获取了Person类中的所有的Filed,以及Public属性修饰的方法,以及默认的构造器!然后判断这个默认的构造器是不是为空,如果不为空的话,就设置一下可访问!

接下来就是三个for循环:

首先第一个就是获取所有的set方法!第二个是获取所有的public属性的变量,第三个就是获取get方法!先看第一个方法:

这里先挨个遍历这些方法,他的方法名字长度是不是小于4,因为set就占了三个字符长度了~ 又判断了这个方法的修饰符是不是静态的,以及方法的返回值是什么,因为set方法一般就是没有返回值的!

当方法是set相关的方法的时候,经过上述的条件判断,一直往下走:

获取到set之后的第一个字母,判断是不是大写的!然后判断了一个静态变量是不是true,如果不是的话,那么就将这个大写字母转换为小写字母!然后将后面的其他字符进行拼接!

之后就是获取这个方法中的变量:

最后通过add方法,把整个获取到信息,全部放入到List里面!

在这个方法中还创建一个FieldInfo,这里我们跟进到这个方法中:

在这个方法中存在一个Feild,对整个逻辑存在相关的影响:

就是这个getOnly变量,正常来说他是一个false,往下走的时候,可以清楚的看到else{ }里面存在着这个变量的覆盖!getOnly=true

正常这个types就是参数的长度,我们这里就是1,如果不是1的话,那就会走到这个else代码里面,然后给getOnly进行赋值!(这里下面也就没有什么其他的东西了,继续往下走就回到了add方法,这里的getOnly有什么用后续再说!)

最终经过上面的for循环,整个List里面就存放了两个值,一个是age,一个是name

之后便是进入到获取类中所有的变量,因为当前的类中是没有Public修饰的变量的,所以就不用看了。

进入到第三个for循环,就是获取get方法,但是这里并不会add(像第一个for循环,寻找set方法时),

因为在这个for循环中,会去判断这个方法的返回值类型是不是Collection或者是不是Map等,如果是的话,才会进行后续的add,还有一个条件就是:

他在找get的时候,会看一下fieldList里面是不是存在这个字段,如果存储过了,那就不会add。所以这里总结一下在寻找get方法的时候,触发add方法的两个条件:

    • 返回值类型需要是Map、Collections等要求的那些
    • 同时fieldList里面没有这个字段(换句话说就是这个字段,只有get方法,而没有set方法!)

最后直接返回了一个JavaBeanInfo,其实在创建反序列化器的整个过程中就是在获取我们这个Person类中的所有的信息!

继续往下走:

下面依然会去通过一些if条件,这个asmEnable还是存在可以修改的情况!比如clazz不是一个接口,默认的构造器是null,同样会修改asmEnablefalse

接下来就是会通过一个for循环!可以遍历Field里面的getonly,如果有一个是true的话,就可以修改asmEnbalefalse了!但是这里并不满足!

最后就会判断这个asmEnable是不是false,如果是的话,就会创建一个JavaBeanDeserializer反序列化器!否则的话,会利用asmFactory去创建一个反序列化器!

所以这里创建的反序列化器并不是默认的那个反序列化器:

而是一个叫FastJsonASMDeserializer的反序列化器!他是一个临时创建的类,所以这里是没办法调试的!回顾整个创建的过程中,其实我们在之前有说到过一个field,就是getonly。当他满足的为true的时候,asmEnable也就变成了false。此时创建的反序列化器就是默认的,此时也就可以进行调试了。

也就是Fieldinfo类中的构造器中,他去获取了方法的参数,判断参数类型的长度是不是1,如果不是的话,就可以进入到else代码中,将getOnly设置为true

那么在往前去找我们从哪里进来这个构造器的:

是在JavaBeanInfo类中的两个for循环中出现的调用!第一个就是寻找set方法,第二个就是寻找get方法!然而在第一个for循环中的FieldInfo里面其实是无法将getOnly设置为true的。原因就是他的参数类型长度肯定是1,所以在第一个for循环中无法设置了!

只能在第二个for循环中进行设置!也就是寻找get方法的for循环。但是之前我们就谈到过这个for循环中的add方法是需要满足条件才能进入的!

他的返回值必须要是if条件里面的才行!所以我们这里需要创建一个返回值是如上类型的set方法!之前还说到一个条件就是 这个field只能有get方法,没有set方法,不然的话,在上面for循环中,将这个field加入到FeildList中就不会再调用get方法了!

所以这里我们定义一个map类型的field

package org.y4y17;

import java.util.Map;

public class Person {
    private String name;
    private int age;
    private Map map;

    public Map getMap() {
        System.out.println("调用了getMap方法");
        return map;
    }

    public Person() {
        System.out.println("调用了constructor方法");
    }
    public String getName() {
        System.out.println("调用了getName方法");
        return name;
    }

    public void setName(String name) {
        this.name = name;
        System.out.println("调用了setName方法");
    }

    public int getAge() {
        System.out.println("调用了getAge方法");
        return age;
    }

    public void setAge(int age) {
        System.out.println("调用了setAge方法");
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

再次调试,直接断点在第二个for循环那里,因为没有给map变量设置set方法,所以前面的寻找set方法的时候,往FieldList里面加入的还是agename两个field

此时是getMap!我们往下跟进:

这里的if条件满足,所以就会进入到这个if条件里面了!

而且在fieldList里面寻找也没找到,原因就是他根本就没有set方法,所以没找到!

到这里的话就继续跟进到Fieldinfo方法里面:

到这里的时候,getMap方法的参数是空的,所以这里就不满足了,成功的进入到了else代码里面,成功的将getOnly设置成了true

最后return。回到上层,继续看:

此时已经是返回了beaninfo,里面的field就是三个了,分别就是agenamemap;继续代码往下走就会经过for循环,去获取fieldgetonly变量,如果是true的话,就会将asmEnable设置为false!然而上面的mapgetonly就已经被我们设置成了true!所以能进入if条件:

这里可以看到mapgetOnly确实就是true。因此可以设置asmEnablefalse

然后往下走成功进入到if条件,创建了一个默认的JavaBeanDeserializer对象!

然后将成功的创建了一个反序列化器,这个是可以调试的。(上述的目的仅仅是为了调试~ )接下来就是利用反序列化器进行反序列化操作了,继续跟进到deserialze方法中:

发现调用了createInstance方法,其中parser就是默认的JSONparsertype便是指定的类,继续跟进:

最终通过构造器进行了newInstance,也就执行了构造器方法!

接着继续往下走便是setvalue

赋值操作无非就是通过反射或者是通过调用set方法!跟进到这个setValue方法中!

然后就是通过invoke进行调用赋值!这里其实有一个if条件,并没有满足,直接跳到了invoke这里执行了。

这里的getOnly变量的值是false。因为他是一个set方法。最后也就返回了整个调用过程和对象:

可以看到这里并没有执行get方法,最开始的时候,就已经提到了上面是调用set方法,而在toJSON的时候才会进行调用get方法!

继续跟进到toJSON里面:

上面一直都在判断这个clazz是个什么?都不满足,因为他是个Person类!

这里的getobjectWriter就是序列化的方法!

接下来就是创建了一个JSONObject对象(和之前的一样就是一个Map

这里就是获取到了三个键值对!然后往JSONObject里面存放!整个过程中也是通过调用invoke方法来实现的get方法执行:

Map<String, Object> values = javaBeanSerializer.getFieldValuesMap(javaObject);

javaBeanSerializer.getFieldValuesMap(javaObject)方法中调用了invoke方法:

继续跟进到这个getPropertyValue方法中:

该方法中又调用了get方法!继续跟进到这个get方法中:

get方法中,先是判断了method是不是为空,如果不为空的话,就通过invoke方法来调用get方法。

此时便成功的完成了整个的调用。整个过程中,我们传递的参数,并没有传递map。如果我们传递map的话,在利用反序列化器进行反序列化的时候,也是会调用getMap的(原因是,寻找set方法的时候,mapfield并没有set方法,仅仅有一个get方法!)

🌸 流程总结

整个过程分为三个阶段

    • JSON解析器解析阶段,此时还只是当作JSON字符串来解析
    • Java反序列化器解析阶段,此时是因为在JSON字符串中找到了@type字段,便开始当作是Java对象解析
    • toJSON阶段,此时会调用get方法

思考:整个流程分析完了,那么如何利用这个漏洞/缺陷来进行攻击呢?

    • 只要能找到一个类,该类中的set或者get方法中存在调用链,便可以利用
🍂 本地Demo

尝试创建一个类,实现弹计算器的操作:

package org.y4y17;

import java.io.IOException;

public class Test {
    public void setCmd(String cmd) throws IOException {
        Runtime.getRuntime().exec(cmd);
    }
}

创建一个Test类!然后我们传递这个类,通过@type,进行指定:

package org.y4y17;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class Main {
    public static void main(String[] args) {
        String s = "{\"@type\":\"org.y4y17.Test\",\"cmd\":\"open -a calculator\"}";
        JSONObject jsonObject = JSON.parseObject(s);
        System.out.println(jsonObject);
    }
}

运行后成功弹出计算器:

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

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

相关文章

【恶意软件检测论文】通过提取 API 语义来实现的一个新颖的安卓恶意软件检测方法

目录 摘要1. 引言2. 相关工作2.1. 基于重新训练的恶意软件检测2.2. 基于应用关系图的恶意软件检测2.3. 基于异常样本识别的恶意软件检测2.4. 基于API聚类的恶意软件检测 3. AMDASE概述4. 基于语义距离的API聚类4.1. API特征提取4.2. API句子生成4.3. API句子编码4.4.聚类中心生…

【iOS】OC高级编程 iOS多线程与内存管理阅读笔记——自动引用计数(四)

目录 ARC规则 规则 对象型变量不能作为C语言结构体的成员 显式转换id和void* 属性 数组 ARC规则 规则 在ARC有效的情况下编译源代码必须遵守一定的规则&#xff1a; 主要解释一下最后两条 对象型变量不能作为C语言结构体的成员 要把对象型变量加入到结构体成员中时&a…

location重定向和nginx代理

文章目录 1 location重定向1.1 概述1.2 rewrite跳转1.3 用例1.4 实验1.4.1 基于域名的跳转1.4.2 基于ip的跳转1.4.3 基于后缀名的跳转 2 nginx的代理2.1 nginx内置变量2.2 正向代理2.2.1 固定正向代理2.2.2 自动代理 2.3 反向代理2.3.1 负载均衡的算法2.3.2 负载均衡的特点2.3.…

【Qt】qt基础

目录 一、使用Qt Creator创建qt项目 二、项目文件解析 三、Qt中创建图形化界面的程序的两种方法 四、对象树 五、Qt中处理打印乱码问题的利器&#xff1a;qDebug() 一、使用Qt Creator创建qt项目 1.选择项目模板 选中第一类模板Application(Qt应用程序&#xff0c;包含普…

CSS在线格式化 - 加菲工具

CSS在线格式化 打开网站 加菲工具 选择“CSS在线格式化” 或者直接访问 https://www.orcc.online/tools/css 输入CSS代码&#xff0c;点击左上角的“格式化”按钮 得到格式化后的结果

java之集合(详细-Map,Set,List)

1集合体系概述 1.1集合的概念 集合是一种容器&#xff0c;用来装数据的&#xff0c;类似于数组&#xff0c;但集合的大小可变&#xff0c;开发中也非常常用。 1.2集合分类 集合分为单列集合和多列集合 Collection代表单列集合&#xff0c;每个元素&#xff08;数据&#xff…

C语言刷题

1. 题目描述 根据给出的三角形3条边a:b.c(a.b,c<100.000)&#xff0c;计算三角形的周长和面积。 输入描述: 一行&#xff0c;三角形3条边(能构成三角形)&#xff0c;中间用一个空格隔开. 输出描述: 一行&#xff0c;三角形周长和面积保留两位小数&#xff0c;中问用一个空…

自动驾驶控制与规划——Project 1: 车辆纵向控制

目录 零、任务介绍一、环境配置1.1 CARLA的配置1.2 Docker Ubuntu 20.04 ROS2 Foxy的配置 二、算法2.1 定速巡航2.2 自适应巡航2.3 离散PID控制 三、代码实现3.1 代码补全3.2仿真验证 零、任务介绍 课程主页 配置Carla仿真器配置carla-ros-bridge补全src\ros-bridge\carla_s…

Linux高并发服务器开发 第一天(Linux的目录结构 cd用法 终端提示符格式)

目录 1.命令解析器&#xff1a;shell 2.LINUX下的目录结构 3.cd的使用 3.1cd 绝对路径 3.2cd 相对路径 3.3cd 回车 3.4cd - 4. 终端提示符格式 1.命令解析器&#xff1a;shell 默认运行与计算机系统终端的 用来解析用户输入命令的工具 内核&#xff1a;操作系统的核…

[SAP ABAP] 序列化与反序列化

1.序列化 序列化表示将ABAP对象类型转成json字符串 我们可以使用方法/ui2/cl_json>serialize实现序列化&#xff0c;可以将ABAP中的内表结构转成json字符串类型 REPORT z437_test_2024.* 自定义数据类型 TYPES: BEGIN OF ty_makt,matnr LIKE makt-matnr, " 物料编号…

【h5py】 提取mat文件中的HDF5格式的数据

h5py 提取mat文件中的HDF5格式的数据 使用纯Python查看数据配合Matlab后&#xff0c;使用Python查看数据 一、使用纯Python查看文件数据内容 原理&#xff1a;当HDF5存储的是struct类型数据&#xff0c;解析时要像一棵树&#xff0c;我们需要逐层次的去解析&#xff0c;直到…

蓝桥杯刷题——day1

蓝桥杯刷题——day1 题目一题干题目解析代码 题目二题干题目解析代码 题目一 题干 给定一个字符串 s &#xff0c;验证 s 是否是 回文串 &#xff0c;只考虑字母和数字字符&#xff0c;可以忽略字母的大小写。本题中&#xff0c;将空字符串定义为有效的 回文串 。 题目链接&a…

el-table行合并及合并后序号处理

效果图 <el-tableclass"ncky-detail-table"v-loading"tableLoading"border:data"tableDataVo":span-method"objectSpanMethod"row-key"uniqueFlag":row-class-name"tablerowclassname"><el-table-column…

WordPress酱茄主题 开源版 博客资讯自媒体网站模板

一款免费开源的WordPress主题&#xff0c;主题专为WordPress博客、资讯、自媒体网站而设计 运行环境 支持WordPress版本&#xff1a;5.6 兼容Chrome、Firefox、Safari等主流浏览器 支持设备&#xff1a;响应式布局&#xff0c;不同设备不同展示效果 服务器环境建议&#x…

解析UnityEditor.TransformWorldPlacementJSON数据

Unity拷贝运行时数据的世界Trans信息可以得到json数据&#xff0c;稍微改装一下就可以得到对象数据了&#xff08;没找到官方对应的接口&#xff0c;如果有知道的可以评论区留言&#xff09;&#xff0c;如下图&#xff1a; 代码如下&#xff1a; [MenuItem("Assets/解析…

【YashanDB知识库】同样建表语句,大整型数字在Oracle插入成功,在YashanDB插入失败

本文内容来自YashanDB官网&#xff0c;原文内容请见 https://www.yashandb.com/newsinfo/7610113.html?templateId1718516 问题现象 在YashanDB上执行Oracle同样的建表语句&#xff0c;插入同样的数据&#xff0c;包含大的整型数字&#xff0c;在Oracle执行成功&#xff0c;…

YOLOv5-Backbone模块实现

YOLOv5-Backbone模块实现 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客 &#x1f356; 原作者&#xff1a;K同学啊 电脑系统&#xff1a;Windows11 显卡型号&#xff1a;NVIDIA Quadro P620 语言环境&#xff1a;python 3.9.7 编译器&#xff1a…

【Linux探索学习】第二十一弹——文件描述符和输出重定向:深入解析Linux操作系统中的文件描述符与输出重定向的底层机制

Linux学习笔记&#xff1a; https://blog.csdn.net/2301_80220607/category_12805278.html?spm1001.2014.3001.5482 前言&#xff1a; 在上一篇&#xff0c;我们已经讲解过文件描述符的相关问题了&#xff0c;但是今天&#xff0c;由于讲解重定向问题需要更进一步理解文件描…

【Spring】使用@Async注解后导致的循环依赖问题

前言&#xff1a;最近遇到一个问题&#xff0c;使用Async注解将方法设置为异步的时候&#xff0c;出现了循环依赖&#xff08;circular reference&#xff09;问题。 1.问题复现 Service public class A implements AInterface {Autowiredprivate BInterface b;AsyncOverride…

数据结构——ST表

ST表的定义 ST表&#xff0c;又名稀疏表&#xff0c;是一种基于倍增思想&#xff0c;用于解决可重复贡献问题的数据结构 倍增思想 这里列举一个去寻找一个区间内的最大值的例子 因为每次会将将区间增大一倍&#xff0c;所以才被称之为倍增思想 &#xff0c;这种思想十分好用…