理解Aop

🧭 整体结构总览

层级 技术视角 控制权体现 提供能力 对上层支撑
1. 计算机硬件 CPU、存储、总线 线性执行+跳转 程序计数器、堆栈、跳转指令 支持有序与跳跃控制
2. 计算机组成原理 存储程序原理、函数调用栈 跳转+调用+返回+中断 函数、递归、上下文切换 抽象为过程式语言
3. 操作系统 用户态、内核态、线程调度 控制权分时复用 中断、线程、信号、调度器 支撑多任务与系统调用
4. 高级语言(Java) 面向对象抽象 控制权→对象方法 封装、组合、多态 构造模块化流程
5. 编译器 & 虚拟机 Java字节码、JVM指令集 映射为执行语义 类加载、动态分派、栈帧管理 支撑语言特性运行
6. 设计模式 代理、装饰器、包装器 控制权结构转移 调用链、对象替换、委托 动态行为插入
7. 框架层(Spring AOP) 动态代理+反射调用链 控制权在入口重构 调用前/后/异常通知 实现面向切面编程

现在我们从第一部分讲起:

🧱 第一部分(修订版):现代处理器的程序执行机制 —— 从线性模型到乱序与预测


🚀 1.1 现代 CPU 执行模型并非“线性逐条”,而是:

乱序执行 + 流水线 + 分支预测 + 超标量执行 + 多级缓存 + 多核并发 + SMT(超线程)

这意味着我们写出来的代码是线性的,而 CPU 实际上在尝试最大限度并行执行它


✅ 1.2 正确认识“程序是线性”的含义

当我们说“程序是线性执行的”时,实际含义是:

层级 控制权表现 实际硬件行为
代码层(汇编) 顺序 + 跳转 线性流程图
编译器中间表示 控制流图(CFG) 静态可控分支结构
CPU 硬件层 乱序调度,指令流水,预测执行 实际顺序不可见但语义一致

🧠 关键点:

  • CPU 不是真的“逐条执行”,而是根据“指令之间的依赖关系图”来动态调整执行顺序;
  • 但最终呈现出来的语义必须与“逐条执行”的结果一致(内存一致性模型)
  • 所以我们把它称为“语义上是线性控制的”,即程序员可以理解为线性控制。

🔄 1.3 现代 CPU 的关键机制

技术 目的 描述
指令流水线 提高吞吐 类似工厂生产线:取指→译码→执行→访存→写回
超标量执行 并行多个指令 每周期可以调度多个指令到多个功能单元
乱序执行(OoOE) 利用执行空隙 对无依赖指令提前执行
分支预测 避免等待 预测 if/else 走向,错误则回滚
重命名寄存器 减少写后读等冲突 避免“虚假依赖”影响并行
Reorder Buffer(ROB) 保证语义一致 保证提交顺序与源码一致
内存一致性模型 多核行为约束 定义了多核读写共享变量的行为

📊 示例:我们看起来的“顺序”实际上是?

Java代码:

int x = a + b;
int y = c * d;

CPU执行:

  1. 编译器生成指令后,CPU可能重排为:
    • LOAD a → reg1
    • LOAD c → reg3
    • LOAD b → reg2
    • LOAD d → reg4
    • MUL reg3, reg4 → reg5(先算乘法)
    • ADD reg1, reg2 → reg6
  2. 最终按正确顺序写回内存或寄存器

你看到的是“线性赋值”,CPU实际上是利用指令之间的无依赖性提前并行执行


📚 推荐资料:

  1. 《Computer Architecture: A Quantitative Approach》 by Hennessy & Patterson(最新版,重点)
  2. 《现代操作系统》Andrew S. Tanenbaum(内存一致性部分)
  3. Intel Architecture Optimization Manual(实际架构说明)

🧱 第二部分:计算机组成原理 —— 栈帧与控制权保存的设计哲学


🎯 目标问题:

如果底层 CPU 是指令线性跳转,程序如何实现函数调用?函数如何知道从哪儿回?参数、局部变量、返回值放哪?如何支持递归?控制权是怎么保存和恢复的?

这些问题的解决,直接支撑了过程式语言的“调用语义”,它构成了高层语言“线性中断再恢复”的设计起点。


📌 2.1 栈帧(Stack Frame):过程式控制的物理支撑

栈帧是程序在调用函数时,为每个函数调用分配的独立执行上下文,放在栈中(栈区),它是过程调用机制的核心。

🧱 栈帧内容(典型 x86/ARM 架构)

区域 含义 为什么需要它
参数区域 函数参数 被调用者使用
返回地址 调用方执行跳转后恢复点 支撑调用-返回
保存寄存器 如 FP、LR、SP 保证调用方环境不变
局部变量区 被调函数内部变量 支持递归、隔离上下文
对齐填充 内存对齐要求 性能优化

🧠 控制权的过程:

main() {
  foo();
}

变成机器层行为:

  1. main调用foo前:
    • PUSH 返回地址
    • PUSH main的寄存器(例如 RBP)
    • MOV 栈指针成新的基址(栈帧切换)
  2. foo内部使用新的栈帧
  3. RET 从栈中弹出返回地址 → 回 main

✅ 控制权在这里“显式保存”+“显式恢复”
✅ 调用链变成了栈帧链(Call Stack)
✅ 支持递归(每次函数都得到自己的独立空间)


🌀 2.2 为什么用“栈”而不是“堆”或“寄存器”?

  • 栈是“后进先出”的结构,正好匹配函数调用的行为(调用 - 调用 - 返回 - 返回)
  • 栈由硬件指针 SP 管理,性能高,且线程独享
  • 不用堆是因为堆需要手动管理生命周期;函数生命周期自动,适合用栈

🔄 2.3 调用约定(Calling Convention):跨模块控制权协约

不同语言/平台之间如何知道:

  • 参数怎么传(栈 / 寄存器)
  • 谁保存寄存器(调用方/被调用方)
  • 返回值放哪(RAX?寄存器?)

这些由 调用约定 决定,如:

  • x86 CDECL
  • x64 System V
  • ARM AAPCS

高层语言只需遵守约定,控制权切换就自动成立 —— 这也是 Java 等语言可以编译为一致字节码的前提。


📚 推荐资料

  • 《CS:APP》第三章 —— 栈帧与过程调用实现
  • 《Computer Systems: A Programmer’s Perspective》
  • Intel x64 Calling Convention Reference

🔗 与上层语言的连接设计哲学

高层语言 编译时映射到 底层支撑
函数调用、return CALL/RET 指令 + 栈帧 保证控制权能转移+恢复
局部变量隔离 每次调用分配新栈帧 递归不冲突
异常/错误处理 栈展开(unwind) 栈帧保留完整调用轨迹

✨ 小结

  • 栈帧 + 控制权保存/恢复机制,是过程式语言成立的基石
  • 函数调用不是“魔法”,而是明确保存返回地址 + 局部上下文,然后跳转,再恢复
  • 这个设计让我们可以从“线性指令”跳跃出“结构化控制流程”
  • 是“控制权跳转”的第一次抽象结构化

✅ 到此,我们已经从“CPU是如何跳转执行指令”进入到了“程序员如何编写有组织的函数调用”,建立了“栈帧是控制权跳跃基础”这个概念支点。

🧱 第三部分:操作系统如何管理“控制权” —— 多任务、线程调度与系统调用


🎯 问题焦点:

如果 CPU 是按照顺序执行的,而函数栈帧也只是单线程控制结构,那么操作系统是如何在多个任务之间管理“控制权”的?线程上下文又是如何保存和切换的?系统调用为何会发生控制权转移?

这些问题,是“控制权可以从用户代码被切到框架 / 操作系统 / 宏观任务”的前提。理解它之后,才能明白:为什么在高层语言中你可以在看似“连续的语义中”被“切入”


📌 3.1 多任务系统的控制权抽象

现代操作系统(如Linux、Windows)采用抢占式多任务调度。这背后的哲学是:

目的 方法 底层机制
让多个任务共享CPU 定期打断当前任务 时钟中断
保证任务状态可恢复 保存当前执行上下文 寄存器 + 栈帧
实现内核/用户分离 控制权“陷入”内核 系统调用(syscall)

🧠 3.2 上下文切换(Context Switch):控制权大迁移

当 OS 进行任务切换时,做了什么?

操作 内容
保存当前任务上下文 寄存器组、PC、SP、FLAGS
选择下一个任务 调度算法:时间片轮转/优先级
恢复目标任务上下文 重新加载寄存器组、SP、PC
继续执行 仿佛从没中断过

🔁 “底层多任务系统的‘控制权保存-恢复’机制,为我们提供了对程序控制流可以非线性处理的启发;但真正让 AOP 成为可能的,是高层语言自身对程序流程的抽象能力 —— 比如面向对象的多态、代理模式、函数调用封装等。这些技术让我们即使在单线程下,也能‘像中途拦截一样’在控制流中插入逻辑。”


🧩 3.3 系统调用:从用户空间陷入内核空间

系统调用(如 open, read, write)表现上是函数,实质上:

  1. 用户程序发起 syscall 指令(如 x86 的 int 0x80syscall
  2. CPU 切换到内核态,保存上下文
  3. 跳转到 syscall handler 执行
  4. 执行完毕后恢复上下文,回到用户态

🧠 用户层代码无法控制何时跳转回来,这构成了一种“隐式控制权转移”。这种机制启发了:

  • 回调函数
  • 中间件链路
  • 拦截器模式

🔗 多线程:控制权的并发结构

一个进程中有多个线程意味着什么?

  • 每个线程都有自己的栈帧、PC、SP、寄存器上下文
  • 线程之间共享内存空间(code/data/heap),但控制权是彼此独立的
  • 线程调度也是一组“上下文切换”的序列

📚 推荐资料:

  1. 《Operating Systems: Three Easy Pieces》
    https://pages.cs.wisc.edu/~remzi/OSTEP/
  2. Linux Kernel Documentation: https://kernel.org/doc/
  3. 《现代操作系统》Tanenbaum(线程、调度、系统调用讲解经典)

✨ 小结

  • 操作系统打破了“线性执行”的假设,使得控制权可以在宏观上被管理、打断、转移
  • 系统调用 / 中断 / 线程切换是非显式的跳转机制
  • 多任务系统中,每个任务/线程就像一个“可被随时挂起/恢复的协程”
  • 高层语言借用了这个思想,构建起“拦截”、“代理”、“注入”的模型

🧭 第四部分(重构):高层语言如何抽象“控制权” —— 控制权转移的结构化设计

目标如果我们要实现“控制权在运行时转移”,那么高层语言必须具备哪些能力、采用哪些结构和模式?


🧱 核心抽象:控制权结构设计的基本单位

我们先界定几个“控制权重构”所依赖的语言特性与结构单位:

抽象能力 描述 支撑的机制
行为抽象 把行为当作一等公民(即函数、方法) 方法、函数指针、闭包
封装 隐藏内部细节,暴露接口 类、模块、访问控制
多态 决定“调用谁”由运行时决定 接口 + 动态分派(如虚方法表)
组合与委托 把行为交给其他对象处理 代理、装饰器
动态调度 动态决定行为执行路径 反射、策略模式、运行时注入

🧩 若要实现“运行中控制权转移”,需要设计出哪些结构?

我们要从“目标”反推设计结构:

目标:我们希望在某段代码执行“之前”和“之后”添加逻辑,而不改变它本身。

🔧 所需的编程能力

功能意图 所需语言能力 常见设计结构
在调用前后插入逻辑 抽象出通用行为接口 策略模式 Strategy
对已有对象行为进行扩展 动态组合行为 装饰器模式 Decorator
替代目标方法的调用路径 控制方法分发路径 代理模式 Proxy
可插拔逻辑 将逻辑作为“数据”处理 函数对象、闭包、反射

🧠 示例推理:

  • 如果你要在方法调用前后插入逻辑 —— 你不能写死调用逻辑,而是应该让方法成为接口的一部分,于是你能“包裹”这个接口,这就是 策略 + 代理
  • 如果你不希望“改动原类”,只能“包裹”它 —— 于是你需要 装饰器
  • 如果你希望运行时再决定是否插入逻辑,你需要“控制行为的组合逻辑” —— 于是你需要 函数对象、反射、拦截器链

📐 控制权重构的四种关键设计模式(语言无关)

模式 结构图示意 控制权转移方式
策略模式 把逻辑“参数化” 决策权外部传入
装饰器模式 多层包裹,前后嵌入逻辑 执行路径变长
代理模式 由代理“接管调用” 原对象不可见
模板方法 抽象骨架 + 允许定制 局部行为重定义

这些模式不是框架发明的,而是“程序结构演化中自然形成的控制权重构手段”。AOP 其实就是这些结构在语言 + 平台 + 工具链的进一步抽象和合成产物


🔭 小结:从目标到设计的自然推导

Q:我们要“在执行中插入行为”,该怎么做?

  • 📌 首先,要有一种“可被插入”的结构 —— 方法必须抽象(接口/基类)。
  • 📌 然后,要有一种“能插入的方法” —— 代理或装饰器。
  • 📌 再然后,要有一种“控制插入时机和顺序”的机制 —— 策略 + 模板 + 链式责任。
  • 📌 最后,为了运行期灵活性,需要动态机制 —— 反射、元数据、函数对象。

🧭 第五部分:编程语言如何将“结构性设计”映射为“运行时控制权转移”

目标:我们已经知道,为了实现“在程序执行中插入逻辑”,需要语言支持某种“结构”——如策略、代理、装饰器等。那么这些结构如何在编程语言层面被实现为运行时可行的控制转移机制


🧱 核心理念:结构 -> 运行时机制的映射

结构设计(编程阶段) 映射到运行时 描述
接口、多态 虚函数表(vtable)或方法表 决定调用哪个方法
装饰器、代理 引用包装、间接调用 控制方法前后插入逻辑
策略模式 函数对象、回调引用 将行为作为数据传递
模板方法 抽象基类,调用受限的钩子方法 父类定义结构,子类参与执行
反射 动态获取、调用方法或字段 不编译期绑定调用关系

⚙️ 实现控制权转移的三种底层机制

1️⃣ 虚函数表(vtable)和动态分派

多态的实现核心——在运行时根据对象实际类型选择方法

  • 每个类在编译时生成自己的 vtable(方法地址表)。
  • 每个对象在构造时指向自己的类的 vtable。
  • 当你写 obj.method() 时,CPU 实际是通过 obj->vtable->method_ptr 跳转。

控制权转移点:方法指针可以在运行时“指向别的实现”。

🔁 因此,我们可以在运行时将某个对象“替换为其子类”或“代理类”,从而改变其方法行为 —— 这就是控制权跳转的基本能力来源。


2️⃣ 引用代理(Reference Wrapping)

所谓“装饰器”、“代理”其实是包装原对象,并持有其引用

  • 新对象实现相同接口,内部持有 realObject
  • 所有方法调用都被转发,中间可以插入其他逻辑(before/after)
class LoggingService implements IService {
   IService target;
   void doSomething() {
      log("before");
      target.doSomething();
      log("after");
   }
}

🔁 控制权转移点:不是目标类自己控制行为,而是由“外部包装器”决定何时以及是否调用原始逻辑。


3️⃣ 反射机制与动态调用

Java(和很多现代语言)允许我们“在运行时”分析类结构,并进行方法调用。

  • Class.forName("com.foo.MyClass") -> 获取类型信息
  • method.invoke(obj) -> 执行方法
  • 支持注解、元数据、字段访问等

🔁 控制权转移点:代码执行的目标和路径完全由运行时逻辑决定 —— 适用于通用框架、插件、自动织入逻辑等。


🔂 示例推导:如果我们只知道接口,如何在运行时控制行为?

🎯 需求:我想在某方法执行前做一些日志记录,但不能修改原始代码

我们要解决的问题可以抽象为:

[原始对象执行逻辑] => 如何在此“外部”加一层逻辑?

那么有两种方案:

  • 🧱 结构层面:我们应该设计一个接口,然后通过代理/装饰器包装原始对象。
  • ⚙️ 运行时层面:我们应该通过 vtable/多态,或者反射方式替换调用路径。

这一切最终的“控制权重定向”,都是源于这些语言层面的机制支持。


🧭 从设计到机制的映射图

策略 / 装饰 / 代理 / 模板方法  ——>(语言结构)接口 + 多态 + 抽象类
        ↓
     包裹、替代
        ↓
 虚函数表 | 引用重定向 | 动态反射
        ↓
   运行时行为改变(控制权转移)

高层语言之所以能够支持“控制权的中间接管”,并非是靠虚构,而是依赖语言结构 + 编译器设计 + 运行时机制共同实现的结果。只要具备:

  • 接口/抽象结构(统一调用约定)
  • 多态/动态分派(替换调用者)
  • 封装与组合(包装原始对象)
  • 运行时信息访问(反射、字节码)

就能构建出控制行为的“调度器”,从而让我们在程序线性执行的过程中,自由地插入、拦截、甚至重定向控制流

🧠 第六部分:JVM 如何支撑控制权模型(以 Java 为例)

目标:现在我们已经知道了语言层支持控制权转移的结构设计,那么这些设计在 JVM(Java 虚拟机)是如何真正运行的?它具体做了什么?又为什么能够“欺骗我们”的肉眼观察,让我们以为程序是“自己在线性地执行”?


🧰 6.1 JVM 执行模型(从源代码到机器行为)

阶段 内容 关键行为
编译期 .java -> .class 编译器生成字节码,保留接口、多态、注解等结构
类加载 .class -> JVM内存结构 由 ClassLoader 动态装入类,解析常量池、继承关系
链接与准备 字段初始化、验证 字段初始为默认值,方法表(vtable)建立
运行期 执行字节码(解释或 JIT 编译) 基于栈帧调用、对象表、多态跳转
GC管理 自动内存管理 对象生命周期由垃圾收集器控制

🔁 控制权切换点

  • 方法调用(虚方法 vs 特殊方法)
  • 类加载顺序控制(懒加载、双亲委派)
  • 动态字节码修改(运行时增强)

🧭 6.2 JVM 的四个关键机制支持“控制权转移结构”

1️⃣ 方法表(Method Table)与动态绑定

  • 每个类加载后在 JVM 内部会生成一个方法表(类似于 C++ 的 vtable)。
  • Java 的 invokevirtualinvokeinterface 指令在执行时,会根据对象实际类型,从方法表中查找正确的方法。
Service s = new LoggingService(new RealService());
s.doSomething(); // 实际执行的是 LoggingService 的方法

结论:我们通过多态 + 代理类,实现了运行时调用的替代 —— 控制权已转移。


2️⃣ ClassLoader 与动态类加载

  • 每个类是由某个 ClassLoader 加载的,不同类加载器可以加载相同类名的不同实现。
  • 可以动态生成字节码并交给自定义 ClassLoader 加载,从而动态插入控制逻辑。

🧠 例子:Spring AOP 的代理类在运行时创建、加载,它不是你写的类,而是一个生成的代理类。


3️⃣ 反射机制:执行逻辑延迟到运行时

  • JVM 提供完整的 java.lang.reflect 包。
  • 利用 Method.invoke(),我们可以:
    • 根据字符串调用方法
    • 动态扫描字段、注解
    • 构造新的对象

🔁 这意味着控制逻辑可以在运行时被“构造出来”——不是写在代码里的,而是“由控制代码生成控制代码”。


4️⃣ 动态代理 & 字节码增强(Instrumentation)

Java 提供了两种通用控制权插入手段:

1. 动态代理(JDK Proxy)

Object proxy = Proxy.newProxyInstance(
  interfaceClass.getClassLoader(),
  new Class[]{interfaceClass},
  (proxy, method, args) -> {
     log("before");
     Object result = method.invoke(realObject, args);
     log("after");
     return result;
  }
);

2. 字节码修改(如 CGLIB, ASM)

  • 可以在类加载前或运行时修改字节码:
    • 在方法头插入日志
    • 替换方法体为其他逻辑
  • 本质是修改 .class 文件中的指令序列。

📌 用于 Spring AOP、Hibernate、MyBatis 等大规模框架自动生成行为。


📊 6.3 JVM 层控制模型总结图

Java语言结构 (接口/多态/代理)
          ↓
编译为 class 文件 (包含元数据/方法签名)
          ↓
类加载器装载类,并构造方法表
          ↓
运行时代理类替代原类成为调用入口
          ↓
通过方法表或反射完成控制权跳转

🚩 结构 -> 平台支撑 的控制跳转链路

层级 实现能力 描述
语言层 抽象、接口、多态 构造行为扩展的结构性入口
编译器 保留元数据、生成虚方法 为 JVM 提供结构支持
虚拟机 方法表、动态类、反射 决定调用路径、生成新逻辑
框架层 动态代理、字节码操作 封装控制权迁移模型

✅ 本节总结

AOP 并不是“魔法”,它本质上是一种:

“结构性控制权转移设计”在 JVM 平台上的实现模型

JVM 本身就支持:

  • 控制调用路径(通过方法表、反射)
  • 动态行为替换(类加载器、代理类)
  • 运行时生成结构(字节码插桩)

这正是我们从 程序线性执行 转变为 结构性可插拔控制执行 的根本支撑。

🎯 最终章:梳理控制权转移的系统性演化


📌 前置:什么是“控制权”?

控制权,是程序中谁来决定“下一步执行什么”的能力。

它在硬件中表现为“PC指针”(程序计数器),在软件中演化为“调用链设计”,在架构中成为“策略控制模型”。


🔧 O1 - Operating System(操作系统):打破线性,建立任务调度

演化视角

  • 硬件本是线性执行(取指、译码、执行),但OS引入“调度器”,让多个程序共用CPU。
  • 引入上下文切换:保存/恢复寄存器、栈指针、程序计数器
  • 提供中断机制:外部事件打断当前执行 → 系统内核响应 → 转移控制权

控制权结构能力

  • 操作系统掌握全局执行主权
  • 提供系统调用作为“受控入口”
  • 引入线程模型:调度粒度细化为线程 → 支持线程级别控制转移

🔁 这为“控制逻辑与业务逻辑解耦”奠定物理基础。


🧱 O2 - Object(对象):引入可组合的抽象执行单元

演化视角

  • 面向对象语言将函数与状态绑定 → 提供封装性、继承性与多态性
  • 接口/抽象类 → 引入“可替换”模型(面向行为编程)
  • 多态(vtable) → 控制流转移点不是代码序列而是“对象表查找”

控制权结构能力

  • 每个对象是一个可组合、可替代的控制单元
  • 接口 + 多态 → 实现运行时可替换、可扩展逻辑的机制
  • 包装器/代理 → 利用对象的组合能力形成“外部控制中枢”

🧠 AOP的核心依赖点之一就在于“对象行为可代理、可插入、可替换”。


👁 O3 - Observer(观察者):实现“非侵入式”的行为感知与插入

演化视角

  • 软件设计从“过程驱动”进入“事件驱动”
  • 观察者模式 → 一个主体被多个观察方监听
  • 发布-订阅机制(Pub-Sub) → 抽象出行为触发点

控制权结构能力

  • 把控制权从“调用链”拆解为“事件链”
  • 插入监听逻辑而不改变原有执行流程(低耦合)
  • 应用于日志记录、监控埋点、权限验证等横切关注点

📌 这正是 AOP 中“通知(Advice)”思想的结构性来源。


🎛 O4 - Orchestrator(控制器/编排器):实现统一协调、动态调度

演化视角

  • 面向对象编程进一步发展为控制反转(IoC)架构
  • 框架(如Spring)成为“统一调度者”,负责管理对象生命周期与依赖关系
  • 动态代理机制被用来“接管”对象行为

控制权结构能力

  • 控制权不在对象自身 → 被框架容器接管
  • 执行行为被“注入”额外控制逻辑(AOP织入点)
  • AOP 作为控制器中的一个调度工具 —— 可定义、插入、组合控制行为

🚀 这使得控制权结构从“硬连接”变为“软插槽”——灵活、高度解耦、可配置。


📊 最终梳理图:控制权演化路径与AOP支撑结构

CPU(线性执行)
   ↓
操作系统(任务调度 & 中断) ← O1
   ↓
语言支持(面向对象 & 多态) ← O2
   ↓
结构设计(观察者 & 事件驱动) ← O3
   ↓
框架容器(IoC控制器 & 插件化) ← O4
   ↓
== AOP 结构性实现入口 ==

✅ 总结:AOP 并非特例,而是“控制权演化”的结构性终点之一

AOP 看似“在中间切入”代码,其本质是通过结构设计与平台支持构造了:

✅ 一个 结构层级明确、行为可配置、控制可插拔 的执行体系

它不是高层语言的幻想,而是:

  • 操作系统提供物理切换基础
  • 面向对象提供行为组合结构
  • 事件/观察者提供松耦合插入机制
  • 控制器容器实现统一管理与执行调度
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
javaJDKjvm

java.sleep()

2025-3-31 8:27:33

ai

自建Y-Chat程序

2025-1-3 14:28:54

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧