forked from mamoe/mirai
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
208 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
# Mirai Console Backend - JVM Plugins - Data Exchange | ||
|
||
> 本章主要介绍如何在多个插件内直接交换数据 | ||
> | ||
> 注: 多插件之间广播事件也是直接交换数据的一种方式 | ||
-------------------------- | ||
|
||
|
||
如果需要在插件之间直接交换数据, 则插件之间必须存在直接或间接的依赖关系。 | ||
|
||
在 [Console - JvmPlugin](JVMPlugin.md#类加载隔离) 中有提到类加载隔离,没有依赖关系的插件之间是不能直接交换数据的。 | ||
|
||
> 关于如何建立依赖关系, 见 [JVMPlugin - 有关插件依赖的说明](JVMPlugin.md#有关插件依赖的说明) | ||
## 在有依赖关系的插件中广播事件 | ||
|
||
现在系统中存在有两个插件 `com.example.guide.baseplugin` 和 `com.example.guide.extplugin`, 其中 `extplugin` 依赖 `baseplugin`。 | ||
|
||
在 `baseplugin` 中定义的类都可以在 `extplugin` 中访问 | ||
|
||
## 在无依赖关系的两个插件中广播事件 | ||
|
||
在两个没有依赖关系的插件中, 是不能直接交换数据的, 即使使用了相同的类名也是不能进行数据交换的。 | ||
|
||
如果需要在两个没有任何关系的插件中交换数据, 需要最少三个模块: `data-typedef`, `plugin-a`, `plugin-b`, `plugin-....` | ||
|
||
### 多插件数据交换核心思路 | ||
|
||
在多个插件间交换数据, 必须存在直接或者间接的关系, 只有建立了关系才能解析到相同的类。 | ||
|
||
两个没有关系的插件 `A` 和 `B` 之间, 必须通过另外的模块 `data-typedef` 建立起间接的关系。 | ||
|
||
比如在 `data-typedef` 中定义的事件 `MyEvent`, 即使该事件是在 A 广播的也可以在 B 监听到。 | ||
|
||
|
||
### 通过修改插件全局类路径链接插件 | ||
|
||
> 不推荐此方法, 仅适合于高度自定义 | ||
> | ||
> - 使用此方法很可能会干扰到其他插件的执行导致其他插件报错。 | ||
> - 使用此方法很可能会遇到库冲突的情况。 | ||
将 `data-typedef.jar` 放入 `plugin-shared-libraries` 即可。 | ||
|
||
模块间的关系如下图 | ||
```text | ||
plugin-a plugin-b others.... ...... | ||
| | | ...... | ||
| | | ...... | ||
| | | ...... | ||
================================================================= | ||
## data-typedef | ||
## ........ | ||
================================================================= | ||
| | ||
================================================================= | ||
## | ||
## MIRAI CONSOLE PLUGIN SYSTEM | ||
## | ||
``` | ||
|
||
### 将 data-typedef 打包成中转插件 | ||
|
||
将 `data-typedef` 也编写为一个 mirai-console 插件, | ||
并在 `plugin-a`, `plugin-b` 中定义对 `data-typedef` 的依赖定义即可。 | ||
|
||
|
||
模块间的关系如下图 | ||
```text | ||
plugin-a plugin-b others.... ...... | ||
| | | ...... | ||
+--- data-typedef ---+ | ...... | ||
| | ...... | ||
================================================================= | ||
## MIRAI CONSOLE PLUGIN SYSTEM | ||
## | ||
``` | ||
|
||
## 排错 | ||
|
||
见 [JVMPlugin Debug](JVMPlugin-Debug.md) | ||
|
||
---------------------- | ||
|
||
> 返回 [JVMPlugin](JVMPlugin.md) | ||
> | ||
> 返回 [开发文档索引](../README.md#mirai-console) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
# Mirai Console Backend - JVM Plugins - Debug | ||
|
||
> 建议在 Java 9+ 的环境中进行排错, mirai-console 在 java 9+ 中的错误堆栈中报告了错误类来自哪个类加载器 | ||
## 错误堆栈基本解析 | ||
|
||
```log | ||
java.lang.Exception: Thread stack dump | ||
at java.base/java.lang.Thread.dumpStack(Thread.java) | ||
at example-plugin.mirai2.jar[shared]//com.example.exmapleplugin.sharedlib.SharedLib.handle(shared.kt:6) | ||
at example-plugin.mirai2.jar[private]//com.example.exmapleplugin.privatelib.PrivLib.cmd(priv.kt:5) | ||
at example-plugin.mirai2.jar//com.example.exmapleplugin.MyCommand.cmd(MyCommand.kt:63) | ||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) | ||
at ...... | ||
at net.mamoe.mirai.console.command.CommandManager.executeCommand$default(CommandManager.kt:125) | ||
at chat-command-0.5.1.jar//net.mamoe.mirai.console.plugins.chat.command.PluginMain.handleCommand(PluginMain.kt:86) | ||
at chat-command-0.5.1.jar//net.mamoe.mirai.console.plugins.chat.command.PluginMain$onEnable$2$1.invokeSuspend(PluginMain.kt:69) | ||
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) | ||
at ...... | ||
``` | ||
|
||
来自 plugin 本身的类加载器的堆栈会以 插件文件名 开头, 其中 `...[private]` `....[shared]` 都是该插件使用的类库. | ||
|
||
- `[shared]` 代表是共享库, 其中的类可以被依赖此插件的其他插件解析到 | ||
- `[private]` 代表是私有库, 仅该插件自己内部使用, 依赖此插件的其他插件将不能解析到此类加载器的类 | ||
|
||
## 多插件间数据交换结果和预期不符合 | ||
|
||
多插件间数据结果不一致 90% 是因为缺少依赖关系导致的未解析到相同的类导致结果不一致 | ||
|
||
关于如何建立关系, 见 [JVMPlugin - Data Exchange](./JVMPlugin-DataExchange.md) | ||
|
||
可以使用以下代码确定是否是因为类链接错误导致的数据不一致 | ||
|
||
```kotlin | ||
fun MiraiLogger.dumpClass(klass: Class<*>) { | ||
info { "Class name: $klass" } | ||
info { " |- loader: ${klass.classLoader}" } | ||
info { " |- module: ${klass.module}" } | ||
} | ||
``` | ||
|
||
```java | ||
public static void dumpClass(MiraiLogger logger, Class<?> klass) { | ||
logger.info("Class name: " + klass); | ||
logger.info(" |- loader: " + klass.getClassLoader()); | ||
logger.info(" |- module: " + klass.getModule()); | ||
} | ||
``` | ||
|
||
## 使用的第三方库报错没有模块实现 | ||
|
||
在插件初始化的时候, 线程上下文类加载器依然还是 console 的系统类加载器 (`AppClassLoader`), 需要手动将其切换到插件的类加载器 | ||
|
||
详见 [Issue Comment](https://github.com/mamoe/mirai/issues/2138#issuecomment-1179673302) | ||
|
||
```kotlin | ||
fun onEnable() { | ||
val oThreadCtxLoader = Thread.currentThread().contextClassLoader | ||
try { | ||
Thread.currentThread().contextClassLoader = javaClass.classLoader | ||
// ....... | ||
} finally { | ||
Thread.currentThread().contextClassLoader = oThreadCtxLoader | ||
} | ||
} | ||
``` | ||
|
||
## java.lang.LinkageError: loader constraint violation | ||
|
||
```log | ||
java.lang.LinkageError: loader constraint violation: when resolving method 'void io.ktor.client.request.HttpRequestBuilder.setMethod(io.ktor.http.HttpMethod)' the class loader 'test-ktor-dev.mirai2.jar' @61dde151 of the current class, com/kasukusakura/testktor/TestKtor$getTailrec$$inlined$get$2, and the class loader 'app' for the method's defining class, io/ktor/client/request/HttpRequestBuilder, have different Class objects for the type io/ktor/http/HttpMethod used in the signature (com/kasukusakura/testktor/TestKtor$getTailrec$$inlined$get$2 is in unnamed module of loader 'test-ktor-dev.mirai2.jar' @61dde151, parent loader 'global-shared' @32b9bd12; io.ktor.client.request.HttpRequestBuilder is in unnamed module of loader 'app') | ||
at ................. | ||
java.lang.LinkageError: loader constraint violation: | ||
when resolving method 'void io.ktor.client.request.HttpRequestBuilder.setMethod(io.ktor.http.HttpMethod)' | ||
the class loader 'test-ktor-dev.mirai2.jar' @61dde151 of the current class, com/kasukusakura/testktor/TestKtor$getTailrec$$inlined$get$2, | ||
and | ||
the class loader 'app' for the method's defining class, io/ktor/client/request/HttpRequestBuilder, | ||
have different Class objects for the type io/ktor/http/HttpMethod used in the signature | ||
( | ||
com/kasukusakura/testktor/TestKtor$getTailrec$$inlined$get$2 is in unnamed module of loader 'test-ktor-dev.mirai2.jar' @61dde151, | ||
parent loader 'global-shared' @32b9bd12; | ||
io.ktor.client.request.HttpRequestBuilder is in unnamed module of loader 'app' | ||
) | ||
``` | ||
|
||
翻译 | ||
|
||
```log | ||
JVM 无法解析 com/kasukusakura/testktor/TestKtor$getTailrec$$inlined$get$2 中的方法引用 | ||
'void io.ktor.client.request.HttpRequestBuilder.setMethod(io.ktor.http.HttpMethod)' | ||
搜索到的 'io.ktor.client.request.HttpRequestBuilder' 位于系统类加载器 'app' 中 | ||
HttpRequestBuilder 中的 HttpMethod 引用和 | ||
TestKtor$getTailrec$$inlined$get$2 得到的 HttpMethod 引用 | ||
不一致, 无法连接 | ||
``` | ||
|
||
结论: 插件没有附带完整的 ktor 依赖导致部分类解析至插件库加载器, 部分类解析至系统类加载器 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters