标记注解 目前, 编译器插件的标记注解有两个:
@SuspendTrans
@SuspendTransProperty
并且它们各自都有一个用于简写的 typealias
:
它们通常作用在类型上或挂起函数上。
举个例子,如下一个接口, 在编译后会产生几个新的非挂起桥接函数:
class Data
interface Foo {
@ST
suspend fun run(): Data
}
class Data
interface Foo {
@ST
@JvmSynthetic // 会自动附加此注解来面向 Java 隐藏
suspend fun run(): Data // 原本的函数
// 生成的阻塞API
fun runBlocking(): Data { ... }
// 生成的异步API,返回值结果是 CompletableFuture
fun runAsync(): CompletableFuture<out Data> { ... }
// 生成的预处理API,返回值结果是 SuspendReserve, 可以用来做进一步转化。
fun runReserve(): SuspendReserve<Data> { ... }
}
生成的桥接函数最终逻辑都仍然是被标记的那个挂起函数。
@STP
与 @ST
的区别在于,前者只能标记在没有参数 的挂起函数上,并且生成的桥接"函数"实际上是属性类型。 还是上面的例子,我们稍作修改, 在编译后会产生几个新的非挂起桥接"属性":
class Data
interface Foo {
@STP // 以属性的形式生成桥接函数
suspend fun value(): Data
}
class Data
interface Foo {
@STP
@JvmSynthetic // 会自动附加此注解来面向 Java 隐藏
suspend fun value(): Data // 原本的函数
// 生成的阻塞API,使用 @STP 的时候,阻塞API的结果默认没有 "Blocking" 后缀。
val value: Data get() { ... }
// 生成的异步API,返回值结果是 CompletableFuture
val valueAsync: CompletableFuture<out Data> get() { ... }
// 生成的预处理API,返回值结果是 SuspendReserve, 可以用来做进一步转化。
val valueReserve: SuspendReserve<Data> get() { ... }
}
这样在 Java 中,使用者便可以以 Getter API 的风格去使用它们了。
标记注解都存在一些参数,可以定制生成的API的函数/属性名。
interface Foo {
@ST(blockingBaseName = "getValue", blockingSuffix = "")
// 生成的阻塞函数的基础名称是 'getValue', 并且去除了后缀名 "Blocking".
// 其他的 API (例如异步API) 不受影响
suspend fun value(): Data
}
interface Foo {
@ST(...)
@JvmSynthetic // 会自动附加此注解来面向 Java 隐藏
suspend fun value(): Data // 原本的函数
// 生成的阻塞API, 名称受到了注解参数的影响而发生变化
fun getValue(): Data { ... }
// 异步API和预处理API
fun valueAsync(): CompletableFuture<out Data> { ... }
fun valueReserve(): SuspendReserve<Data> { ... }
}
当使用编译器插件标记注解的参数调整了生成的桥接函数的签名后, 它的所有实现/继承者, 如果也需要添加标记注解, 那么参数请尽可能保证是 相同的 , 否则会产生多套桥接函数, 造成预期外的影响。
实现挂起函数时 当你实现 Event
、 Bot
等 simbot4 标准库中具有挂起函数的类型时,它们基本上都已经通过此插件做了转化。
你需要观察:
Ⅰ: 如果继承的函数的返回值对于你来说 没有 进一步扩展
那么你可以直接将其标记 @JvmSynthetic
:
interface SourceType { // 假设这是个你需要去实现的某个类型
@ST // 它标记了一个编译器插件的注解,说明此函数会生成对应的非挂起函数
suspend fun run(): Int // 返回值是 Int, 它没有(也不可能)有进一步扩展类型
}
class MySourceType : SourceType { // 假设这是你提供的实现类
@JvmSynthetic // 只使用 @JvmSynthetic: 因为你不需要生成配套的非挂起函数来覆盖
override suspend fun run(): Int {
return ...
}
}
Ⅱ: 继承的函数的返回值对于你来说 有进一步扩展
那么你需要标记一个与被继承函数相同 的编译器插件标记注解。
举个例子:
假设你需要实现的:
open class Data
interface SourceType { // 假设这是个你需要去实现的某个类型
@ST(blockingBaseName = "getValue")
// 它标记了一个编译器插件的注解 @ST,说明此函数会生成对应的非挂起函数, 并且有 blockingBaseName 参数
suspend fun value(): Data // 返回值是 Data, 你扩展了它
}
假设你的实现:
class MyData : Data() // 你对 Data 类型的扩展实现
class MeSourceType : SourceType { // 假设这是你提供的实现类
@ST(blockingBaseName = "getValue")
// 你必须标记与被继承函数 完全相同 的标记注解,包括它的参数
override suspend fun value(): MyData { // 这里修改返回值, 让它返回你的进一步扩展的类型
return ...
}
}
在 simbot4 标准 API 中,大部分被标记的挂起函数都没有参数,因此注意它标记的注解即可, @ST
或 @STP
中的一个。 注解有可能会被标记在类上而不是方法上,可以注意观察。
但也并非没有具有特殊参数的API,目前,它们分布在如下地方:
它们主要就是将参数里的各个 xxxBaseName
重新指定为了 Getter 风格的 getXxx
, 例如 GuildRelation.guild(...)
:
public interface GuildRelation {
@ST(blockingBaseName = "getGuild", blockingSuffix = "", asyncBaseName = "getGuild", reserveBaseName = "getGuild")
// 将 baseName 都重新指定为了 getGuild, 并且移除了阻塞API的后缀。
public suspend fun guild(id: ID): Guild?
public val guilds: Collectable<Guild>
@STP
public suspend fun guildCount(): Int
}
之所以这么做,是因为这些API实际上都比较符合 Getter 的风格,但是因为它们都有参数,而不能直接使用 @STP
, 所以便使用这种方式手动配置来生成 Java 中更为友好也更符合习惯的 API。