# 4. 看懂最小插件到底发生了什么

到了这里，我们先暂时停一下，不继续往插件里加新东西。

这是一个很重要的时机。因为你现在手里已经有了一个最小插件，如果这时候不回头看一眼它到底是怎么被系统接受的，后面很容易进入一种“会照着写，但不知道为什么”的状态。

这章要做的，就是把上一章那个最小插件重新拆开，顺着系统真正的加载路径走一遍。不是为了把源码每一行都解释给你听，而是为了让你脑子里建立一条更稳定的认识：

> **一个插件从放进 `plugins` 目录，到最后变成系统里的可用组件，中间到底发生了什么。**

### 4.1 先 view 全流程，不急着钻细节

如果把上一章的 `echo_demo` 插件压缩成一条最简路径，大概会是这样：

```text
放进 plugins 目录
-> 被扫描发现
-> 读取 manifest.json
-> 导入 plugin.py
-> 注册插件类
-> 实例化插件
-> 获取组件列表
-> 生成组件签名
-> 注册到全局注册表
-> 成为系统可用能力
```

你可以先盯着这条链看两遍。

因为后面不管你写的是 Command、Service、Tool，还是更复杂的多组件插件，本质上都离不开这条主线。区别只在于：插件越复杂，这条链上的每个环节携带的信息会越多。

而对于最小插件来说，它的价值恰恰就在这里。它足够小，所以你可以很容易看清楚每个环节分别负责什么。

### 4.2 第一步：系统先发现你的插件

插件系统并不会自动知道“你最近新建了一个 echo_demo 插件”。它首先要做的，是去插件目录里找。

对当前项目来说，默认插件目录就是项目根目录下的 `plugins`。

所以当你把这个结构放进去：

```text
plugins/
└── echo_demo/
    ├── manifest.json
    └── plugin.py
```

系统看到的第一件事，其实不是 `plugin.py`，而是：

> 这里有一个目录，目录里有 `manifest.json`，它看起来像一个插件。

这一步听起来普通，但它其实是在划定插件边界。因为系统并不是看“这个目录里有没有 Python 文件”，而是看“这个目录是不是一个满足插件基本约定的单元”。

这也是为什么 manifest 这么重要。它不仅仅是元信息文件，它其实还是插件能否进入加载流程的第一道门。

### 4.3 第二步：manifest 先回答“你是谁”

一旦插件目录被发现，系统接下来会先读取 `manifest.json`。

在入门阶段，你可以把 manifest 理解成插件在加载前递交给系统的一张说明卡。它主要帮系统回答这些问题：

- 这个插件叫什么。
- 版本是什么。
- 入口文件在哪里。
- 依赖哪些其他插件或组件。
- 最低核心版本要求是什么。

如果你回头看上一章的最小示例，会发现它其实已经把这几个最关键的信息都交代了。

这里有一个很值得建立的直觉：

> **manifest 负责让系统“认识这个插件”，但它本身并不执行插件逻辑。**

也就是说，manifest 更像是“装载说明”，而不是“功能实现”。

这也是为什么当 manifest 写错时，插件往往会连导入阶段都进不去。系统在那个阶段还没开始跑你的命令组件，它只是先发现：这张说明卡本身就对不上。

### 4.4 第三步：系统根据入口文件导入插件模块

当 manifest 通过基础检查之后，系统才会根据 `entry_point` 去导入真正的插件模块。

对于我们的例子来说，这个入口就是：

```text
plugin.py
```

这一步的重点不是“把文件打开”，而是“把模块导入到 Python 运行时里”。

为什么这很重要？因为导入模块时，模块顶层代码会执行。也就是说，像下面这种写法：

```python
@register_plugin
class EchoDemoPlugin(BasePlugin):
    ...
```

并不是等你以后手动调用时才生效，而是在 `plugin.py` 被导入时，`@register_plugin` 就已经参与工作了。

这一步其实很像系统在说：

> 好，我已经读到了你的插件入口。现在请你把“谁才是这个插件的主类”明确告诉我。

### 4.5 第四步：@register_plugin 把插件类挂进系统里

这就是 `@register_plugin` 真正发挥作用的地方。

它解决的不是“这个类能不能工作”，而是“系统以后怎么把这个插件类找回来”。

如果没有这一步，系统即使成功导入了 `plugin.py`，也仍然可能不知道哪个类才是你的插件根类。对于人来说，你看一眼文件也许就知道 `EchoDemoPlugin` 是主角；但对运行时来说，它需要一个明确、可检索的注册动作。

所以你可以把 `@register_plugin` 理解成这样一句话：

> **请把这个类登记为当前插件系统中的正式插件类。**

到了这里，插件才真正开始从“磁盘上的一组文件”转变成“运行时里可被管理的对象”。

### 4.6 第五步：插件类被实例化，插件开始有了“运行时实体”

类被注册之后，系统下一步不是立刻去执行命令，而是先把插件类实例化。

这一步很关键，因为从现在开始，插件不再只是一个类定义，而是一个实际存在的插件对象。后面组件注册、生命周期钩子、配置注入，都是围绕这个插件实例展开的。

这一点很容易被初学者忽略。因为我们平时写示例时，经常只盯着类定义看，仿佛“类写出来了，插件就算存在了”。

其实不是。

类只是蓝图。**被系统实例化之后，它才真正进入运行时。**

### 4.7 第六步：系统向插件要“你包含哪些组件”

到了这一步，系统终于开始追问插件真正提供的能力了。

它不会自己猜，也不会去扫描目录里所有看起来像组件的类。当前实现里，更直接的方式是：

> 由插件类通过 `get_components()` 明确返回它包含哪些组件。

这也是为什么我们在上一章要写：

```python
def get_components(self) -> list[type]:
    return [EchoCommand]
```

这句代码看起来很短，但它的语义其实非常明确：

> 当前这个插件，正式交给系统注册的组件，就是这个命令组件。

注意，这里返回的是**组件类**，而不是组件实例。

这样设计的好处是，系统仍然掌握组件注册、实例化和调度的主导权。插件作者负责“声明有哪些组件”，系统负责“把这些组件纳入运行时管理”。

### 4.8 第七步：系统识别组件类型，并生成组件签名

当系统拿到 `EchoCommand` 之后，还得继续做两件事：

- 判断它属于哪种组件类型。
- 为它生成唯一签名。

第一件事是通过基类来判断的。因为 `EchoCommand` 继承自 `BaseCommand`，所以系统会把它识别为 Command 组件。

第二件事，则是构建它在系统中的唯一名字，也就是组件签名。

对于我们的例子来说，这个签名最终会长成：

```text
echo_demo:command:echo
```

你可以把它拆成三部分看：

- `echo_demo`：这个组件属于哪个插件。
- `command`：这个组件是什么类型。
- `echo`：这个组件的名字是什么。

这一步的意义非常大。因为从系统视角看，真正稳定地识别一个组件，靠的不是 Python 类名，而是这个签名。

后面无论是依赖、状态、查找，还是插件间协作，都会不断回到这个坐标系上来。

### 4.9 第八步：组件被注册进全局注册表

当签名构建出来之后，系统就会把组件注册到全局注册表中。

你可以先把这个注册表想成一份系统级清单：

- 当前有哪些组件已经被系统接纳。
- 它们分别属于哪个插件。
- 它们是什么类型。
- 它们声明了哪些依赖。

一旦 `EchoCommand` 被注册进去，它就不再只是 `echo_demo/plugin.py` 里的一个类定义，而是变成了系统已知的正式组件。

这一步之后，命令管理器、权限系统、帮助信息系统、后续的运行时执行逻辑，才有机会真正找到它。

换句话说：

> **注册表把“插件作者写的类”，变成了“系统可以统一管理的组件”。**

### 4.10 第九步：状态被标记，插件真正进入“已加载”阶段

除了注册组件本身，系统还会顺手记录状态。

这件事在最小插件里看起来可能没什么存在感，但它其实是在为后面的复杂能力打基础。因为一个系统如果想可靠地管理插件和组件，就不能只知道“它存在”，还得知道“它现在处于什么状态”。

比如说，一个插件可能是：

- 已加载
- 已激活
- 未加载
- 出错

同理，组件也有自己的状态轨迹。

在入门阶段，你暂时不需要把状态管理的内部细节全记住。但至少要知道：**加载成功不是一句日志，而是系统内部会被记录下来的运行时事实。**

### 4.11 为什么最小插件值得回头这样拆

如果你现在回头看上一章那个 `echo_demo`，你会发现它的每一小块代码，其实都不是随便放在那里的：

- `manifest.json` 让系统知道插件的身份和入口。
- `plugin.py` 提供真正的插件模块。
- `@register_plugin` 让插件类能被系统找回。
- `BasePlugin` 告诉系统“这是插件根类”。
- `get_components()` 告诉系统“这个插件包含哪些组件”。
- `BaseCommand` 告诉系统“这是组件类型”。
- `command_name` 参与生成最终组件签名和命令入口。

这就是为什么我一直不建议一开始只把示例代码复制进去就算结束。

因为你一旦理解了这些位置分别在回答什么问题，后面再写第二个、第三个插件时，思路就会稳很多。你不会再只是机械模仿结构，而会开始主动判断：

- 哪些信息该写进 manifest。
- 哪些逻辑该放在插件类。
- 哪些能力该交给组件。
- 哪些字段只是名字，哪些字段会真正影响运行时行为。

### 4.12 把这一章压缩成一句话

如果你想把这一章压缩成一句最容易带走的话，那我会建议你记住这句：

> **插件加载，本质上是系统根据 manifest 找到入口模块，再根据注册过的插件类，把组件一步步纳入运行时管理。**

这句话听起来有点技术味，但它非常值得记住。因为从这里开始，你已经不只是会“做一个插件”，而是真的开始明白：**系统为什么会承认这个插件。**

而一旦你明白系统承认插件的方式，后面再去谈目录拆分、配置注入、依赖协作，就都会自然很多。

下一章，我们就继续往前走。不过这一次，关注点不再是“插件怎么被加载”，而是“一个插件写大之后，目录结构应该怎么组织，才不会越写越乱”。
