跳转到内容

kavaref-core ​

Maven CentralMaven metadata URL

这是 KavaRef 的核心依赖,你需要引入此模块才能使用 KavaRef 的基本功能。

配置依赖 ​

你可以使用以下方式将此模块添加到你的项目中。

我们推荐你优先参考 kavaref-bom 使用 BOM 统一管理版本。

在你的项目 gradle/libs.versions.toml 中添加依赖。

toml
[versions]
kavaref-core = "<version>"

[libraries]
kavaref-core = { module = "com.highcapable.kavaref:kavaref-core", version.ref = "kavaref-core" }

在你的项目 build.gradle.kts 中配置依赖。

kotlin
implementation(libs.kavaref.core)

请将 <version> 修改为此文档顶部显示的版本。

传统方式 ​

在你的项目 build.gradle.kts 中配置依赖。

kotlin
implementation("com.highcapable.kavaref:kavaref-core:<version>")

请将 <version> 修改为此文档顶部显示的版本。

特别注意

从 1.1.0 起,为了兼容每个平台分发的二进制文件差异,KavaRef 引入了全新的底座模块,更新到这个版本时,你必须选择一个平台相关的底座模块作为依赖引入,否则会引发 NoClassDefFoundError 异常。

Android 平台请选择 kavaref-android,JVM 平台请选择 kavaref-jvm。

功能介绍 ​

你可以 点击这里 查看 KDoc。

基本用法 ​

KavaRef 采用链式调用的设计方案,它对可用的 Java 反射 API (例如 Class) 创建了扩展方法,你只需要对这些内容调用 resolve(),即可进入 KavaRef 的世界。

关系图如下。

KavaRef
└── KClass/Class.resolve()
    ├── method()
    ├── constructor()
    └── field()

接下来,我们将给出多个示例的 Java Class,后续都将基于它们进行基本的反射方案讲解。

java
package com.demo;

public class BaseTest {

    public BaseTest() {
        // ...
    }

    private void doBaseTask(String taskName) {
        // ...
    }
}
java
package com.demo;

public class Test extends BaseTest {

    private Test() {
        // ...
    }

    private static TAG = "Test";

    private boolean isTaskRunning = false;

    private void doTask(String taskName) {
        // ...
    }

    private void release(String taskName, Function<boolean, String> task, boolean isFinish) {
        // ...
    }

    private void stop() {
        // ...
    }

    private String getName() {
        // ...
    }
}
java
public class Box<T> {

    public void print(T item, String str) {
        // ...
    }
}

假设,我们想要得到 Test 的 doTask 方法并执行,在 KavaRef 中,你可以通过以下方式来实现。

示例如下

kotlin
// 假设这就是这个 Class 的实例
val test: Test
// 通过实例化 Class 的方式对其进行反射
// 在 KavaRef 中,你无需将其转换为 `java.lang.Class`,
// 它会自动调用 KClass.java
Test::class
    // 创建 KavaRef 反射
    .resolve()
    // 创建 Method (方法) 条件
    .method {
        // 设置方法名
        name = "doTask"
        // 设置方法参数类型
        parameters(String::class)
    }
    // 条件执行后会返回匹配到的 List<MethodResolver> 实例
    // 这里我们获取到过滤结果的第一个
    .first()
    // 在 MethodResolver 上设置 Test 的实例
    .of(test)
    // 调用方法并传入参数
    .invoke("task_name")

在以上写法中,我们通过 Test::class.resolve() 来获取当前 Class 的 KavaRef 反射实例, 然后通过 method { ... } 来创建一个方法过滤条件 MethodCondition,在其中设置方法名和参数类型,执行后返回 List<MethodResolver> 实例, 接着我们通过 first() 来获取第一个匹配到的 MethodResolver 实例, 然后通过 of(test) 来设置当前 Class 的实例,最后通过 invoke("task_name") 来执行方法并传入参数。

在这其中,MethodCondition 继承自 MemberCondition,它允许你对 Method 进行条件筛选,其中包含了 Java 核心的反射 API 的条件镜像,你可以查看对应的注释来了解每个 API 的原生用法。

同样地,MethodResolver 继承自 MemberResolver,它允许你对过滤结果中的 Method 进行反射调用。

由于这里的反射需求是得到一个可用的方法结果,所以 method { ... }.first() 的调用链可能来起来会比较繁琐,这个时候就有以下简化方案。

示例如下

kotlin
Test::class
    .resolve()
    // 直接使用 firstMethod 来获取第一个匹配到的 MethodResolver 实例
    .firstMethod {
        name = "doTask"
        parameters(String::class)
    }
    .of(test)
    .invoke("task_name")

由于我们现在可以拿到 Test 的实例,那么还有一种简化写法,你可以直接使用这个实例创建 KavaRef 反射。

示例如下

kotlin
// 在这里,Test 的实例 test 会被传给 KavaRef 并获取 test::class.java
test.asResolver()
    .firstMethod {
        name = "doTask"
        parameters(String::class)
    } // 由于你设置了实例,所以这里不再需要 of(test)
    .invoke("task_name")

特别注意

asResolver() 总是会将调用者作为实例,它不能用于 Class 或 KClass,在 Class 或 KClass 上直接调用将会出现编译错误,请使用 resolve() 来反射一个类中的成员。

如果调用者的类型被声明为 Any 等类型,但实际上是一个 Class 或 KClass 对象,它同样会被作为实例,此时反射的是 Class 或 KClass 自身的成员。

接下来,我们需要得到 isTaskRunning 变量,可以写作以下形式。

示例如下

kotlin
// 假设这就是这个 Class 的实例
val test: Test
// 使用 KavaRef 调用并执行
val isTaskRunning = test.asResolver()
    .firstField {
        name = "isTaskRunning"
        type = Boolean::class
    }.get<Boolean>()

Test 中的构造方法是私有化的,现在,我们可以使用以下方式来创建它的实例。

示例如下

kotlin
val test = Test::class.resolve()
    .firstConstructor {
        // 对于零参构造方法,可以使用以下条件过滤
        // 它等价于 parameterCount = 0
        // 请不要写成 parameters(),不传入参数类型时它不会添加任何条件
        emptyParameters()
    }.create() // 创建一个新的 Test 实例

你也可以使用 createAsType<T>() 为实际对象 Test 指定其超类类型 BaseTest。

示例如下

kotlin
val test = Test::class.resolve()
    .firstConstructor {
        emptyParameters()
    }.createAsType<BaseTest>() // 创建一个新的 BaseTest 实例

小提示

除了 firstMethod 等方法外,你也可以使用 lastMethod 等方法来获取最后一个匹配到的 MethodResolver 实例,它等价于 method { ... }.last()。

在得到 MemberResolver 实例后,你可以使用 self 来获取当前 MemberResolver 的 Member 原始实例来对其进行一些你自己的操作。

在继承于 InstanceAwareResolver 的 MemberResolver 中 (例如 MethodResolver 和 FieldResolver),你都可以使用 of(instance) 来设置当前实例,如果反射得到的是静态 (static) 成员,你无需设置实例。

注意

get<T>()、invoke<T>() 等带类型的方法不会检查实际类型,类型不一致时将在使用结果时抛出 ClassCastException。

Any.resolve() 方法已在 1.0.1 版本被弃用,在 1.0.2 版本被移除,因为它会污染命名空间 (例如 File.resolve("/path/to/file")),现在请使用 Any.asResolver() 来代替。

特别注意

在继承于 InstanceAwareResolver 的 MemberResolver 中,of(instance) 的类型要求与当前反射的 Class 实例泛型类型相同, 除非不指定 Class 泛型类型,或将 Class 泛型类型设置为 Any。

如果 of(instance) 出现 Required: Nothing? 错误 (这通常由于 Class 通过 Class.forName(...) 或 ClassLoader.loadClass(...) 创建), 则是你的 Class 为 Class<*> (Java 中是 Class<?>),此时如果你不想指定类型,请设置或转换为 Class<Any>,就像下面这样。

示例如下

kotlin
val myClass = Class.forName("com.xxx.MyClass") as Class<Any>
// 假设这就是这个 Class 的实例
val myClassInstance: Any
myClass.resolve()
    .firstMethod {
        // ...
    }.of(myClassInstance).invoke(...)

你也可以使用 kavaref-extension 中提供的 创建 Class 对象 来解决这个问题。

模糊条件 ​

你会注意到 Test 中有一个 release 方法,但是它的方法参数很长,而且部分类型可能无法直接得到。

此时,你可以借助 parameters(...) 条件使用 VagueType 来填充你不想填写的方法参数类型。

示例如下

kotlin
// 假设这就是这个 Class 的实例
val test: Test
// 使用 KavaRef 调用并执行
Test::class.resolve()
    .firstMethod {
        name = "release"
        // 使用 VagueType 来填充不想填写的类型,同时保证其它类型能够匹配
        parameters(String::class, VagueType, Boolean::class)
    } // 得到这个方法

注意

VagueType 只能在有多个参数的过滤条件时使用,它不可以在只能设置单个参数的过滤条件中使用,例如 type。

你可以使用 VagueType、VagueType::class 或 VagueType::class.java 来创建,它们都能被正确识别为模糊过滤条件。

自由条件 ​

在 MemberCondition 中,name、type、parameterCount 等条件都可以使用 Kotlin lambda 特性创建自由过滤条件。

假设我们要得到 Test 中的 doTask 方法,可以使用以下实现。

示例如下

kotlin
// 假设这就是这个 Class 的实例
val test: Test
// 使用 KavaRef 调用并执行
Test::class.resolve()
    .firstMethod {
        // 使用 lambda 来设置方法名
        name {
            // 设置名称不区分大小写
            it.equals("dotask", ignoreCase = true)
        }
        // 设置参数类型
        parameters(String::class)
    }.of(test).invoke("task_name")

小提示

所有条件都会按照 KavaRef 内部固定的顺序进行过滤,而不是按照你编写的顺序,一个成员一旦被某个条件排除,后续的条件将不再对它执行。

如果自由条件在过滤某个成员时抛出了异常,这个成员会被视为不匹配,所以你可以只针对你关心的成员编写自由条件。

如果最终没有找到任何成员,这些异常会在找不到成员的异常信息中列出,详情请参考 异常处理。

泛型条件 ​

KavaRef 支持添加泛型过滤条件,你可以使用 TypeMatcher 提供的相关功能来实现。

假设我们需要过滤 Box<String> 中的 print 方法。

示例如下

kotlin
// 假设这就是这个 Class 的实例
val box: Box<String>
// 使用 KavaRef 调用并执行
box.asResolver()
    .firstMethod {
        name = "print"
        // 设置泛型参数条件
        genericParameters(
            // 过滤泛型名称 "T"
            typeVar("T"),
            // 通过 Class 创建 TypeMatcher
            String::class.toTypeMatcher()
        )
    }.invoke("item", "str")

在超类过滤 ​

你会注意到 Test 继承于 BaseTest,现在我们想得到 BaseTest 的 doBaseTask 方法。

在不知道超类名称的情况下,我们只需要在过滤条件中加入 superclass() 即可实现这个功能。

示例如下

kotlin
// 假设这就是这个 Class 的实例
val test: Test
// 使用 KavaRef 调用并执行
Test::class.resolve()
    .firstMethod {
        name = "doBaseTask"
        parameters(String::class)
        // 只需要添加这个条件
        superclass()
    }.of(test).invoke("task_name")

这个时候我们就可以在超类中获取到这个方法了。

小提示

superclass() 一旦设置就会自动循环向后过滤全部继承的超类中是否有这个方法,直到 java.lang.Object 为止 (包括 java.lang.Object 本身)。

superclass() 仅对当前的过滤条件生效,在同一个 resolve() 中创建的其它过滤条件不会受到影响。

特别注意

当前过滤的方法除非指定 superclass() 条件,否则只能过滤到当前 Class 的方法,这是 Java 反射 API 的默认行为, KavaRef 会调用 Class.getDeclaredMethods() 来获取当前 Class 的方法而不是 Class.getMethods()。

如果你还需要过滤接口中的成员,例如接口中的 default 方法,可以使用 superclass(interfaces = true)。

示例如下

kotlin
// 假设这就是这个 Class 的实例,它实现了声明 doApiTask 方法的接口
val test: Test
// 使用 KavaRef 调用并执行
Test::class.resolve()
    .firstMethod {
        name = "doApiTask"
        // 在超类与接口中过滤
        superclass(interfaces = true)
    }.of(test).invoke()

小提示

superclass(interfaces = true) 会先按照 superclass() 的方式过滤全部超类,然后再过滤全部接口,子接口总是会在其父接口之前被过滤。

接口中的 default 方法、静态方法与常量都可以被过滤到。

注意

在 Android 中,如果应用的 minSdk 低于 24,D8 会在编译时将接口中的静态方法移动到其生成的辅助类中,此时无法在接口中过滤到这些静态方法。

更多条件 ​

KavaRef 提供了一些过滤条件来辅助 Java 反射 API 的使用。

假设我们要得到 Test 中的静态变量 TAG 的内容。

为了体现过滤的条件包含静态描述符 (static),我们可以使用以下方式来实现。

示例如下

kotlin
val tag = Test::class.resolve()
    .firstField {
        name = "TAG"
        type = String::class
        // 创建描述符过滤
        modifiers(Modifiers.STATIC)
        // 或者
        modifiers {
            it.contains(Modifiers.STATIC)
        }
    }.get<String>() // 获取字段内容

你还可以在 type、parameters 等条件中使用字符串类型传入完整类名。

示例如下

kotlin
// 假设这就是这个 Class 的实例
val test: Test
// 使用 KavaRef 调用并执行
Test::class.resolve()
    .firstMethod {
        name = "doTask"
        // 使用字符串类型传入完整类名
        parameters("java.lang.String")
    }.of(test).invoke("task_name")

如果要过滤结果为空的情况,例如不声明任何异常的方法,请使用 emptyExceptionTypes() 这类以 empty 开头的方法,不传入参数的 exceptionTypes() 不会添加任何条件。

异常处理 ​

在默认情况下,KavaRef 会在反射调用过程中找不到成员时抛出异常。

示例如下

kotlin
Test::class.resolve()
    .method {
        name = "doNonExistentMethod"
    } // 这里会抛出 NoSuchMethodException

如果你不希望抛出异常,可以设置可选条件 optional()。

示例如下

kotlin
Test::class.resolve()
    // 设置可选条件
    .optional()
    .method {
        name = "doNonExistentMethod"
    } // 返回空的 List<MethodResolver>

KavaRef 会打印完整的异常内容以供调试,在使用 optional() 时,异常会以 WARN 级别的日志打印。

示例如下

No method found matching the condition for the current class.
+------------------------------------------------+
| class com.demo                                 |
+------------+-----------------------------------+
| name       | doNonExistentMethod               |
| parameters | [class java.lang.String, boolean] |
+------------+-----------------------------------+

如果 自由条件 在过滤过程中抛出了异常,这些条件会在表格中以 [!] 标记,并在下方按照条件名称列出抛出异常的成员及其完整的异常堆栈。

如果所有成员在到达某个自由条件之前就已经被其他条件排除,这个自由条件在本次过滤中不会被执行,它会在表格中以 [-] 标记。

示例如下

kotlin
Test::class.resolve()
    .method {
        // 返回类型不是数组的方法,componentType 为 null,这里会抛出 NullPointerException
        returnType { it.componentType.name == "java.lang.String" }
        // 所有方法已经被上面的条件排除,这个条件不会被执行
        genericReturnType { it is ParameterizedType }
    } // 这里会抛出 NoSuchMethodException

此时打印的异常内容如下。

示例如下

No method found matching the condition for the current class.
+------------------------------------------------------+
| class com.demo                                       |
+----------------------------+-------------------------+
| returnTypeCondition        | [!] (Runtime Condition) |
| genericReturnTypeCondition | [-] (Runtime Condition) |
+----------------------------+-------------------------+
Suggestion: ...
[!] Exceptions thrown by runtime conditions:
[returnTypeCondition]
- public void com.demo.doTask(java.lang.String)
java.lang.NullPointerException: ...
    at ...

在使用 optional() 时,WARN 级别的日志中同样会包含这些内容。

如果你不希望 KavaRef 抛出或打印任何内容,你可以使用 optional(silent = true) 静默化处理,但是我们不建议这样做,这会掩盖问题,除非有必要这么做。

特别注意

如果你设置了 optional(),那么请不要使用 firstMethod、firstConstructor 等方法来获取单个结果, 因为它们会在没有结果时抛出列表为空的异常,你可以使用后缀为 OrNull 的方法来获取单个结果。

但是这里需要注意一个事情,如果你没有设置 optional(),那么 firstMethodOrNull 等方法依然会在没有结果时抛出异常,这是预期行为,因为 method { ... } 才是过滤器的 “构建” 操作,异常在此处理,firstMethodOrNull 等方法只是一个封装,是对结果 List 是否为空的 Kotlin 自身标准库异常处理,不参与 KavaRef 过滤器的异常处理。

所以你一定要像下面这样做。

示例如下

kotlin
Test::class.resolve()
    // 设置可选条件
    .optional()
    .firstMethodOrNull {
        name = "doNonExistentMethod"
    } // 返回 MethodResolver 或 null

日志管理 ​

KavaRef 提供了其自身的日志管理功能,你可以通过 KavaRef.logLevel 来设置日志级别。

你可以设置 KavaRef.logLevel = KavaRefRuntime.LogLevel.DEBUG 来启用 DEBUG 级别的日志使得 KavaRef 在过滤过程向控制台打印更为详细的分步过滤条件日志。

如果你想关闭 KavaRef 的全部日志打印,你可以设置 KavaRef.logLevel = KavaRefRuntime.LogLevel.OFF。

小提示

在 JVM 平台,KavaRef 默认会将日志打印到标准错误输出 (System.err),你无需引入任何日志框架。

在 Android 平台,KavaRef 使用 android.util.Log 作为日志打印的实现,你无需额外配置。

无论在哪个平台,KavaRef 的日志级别都仅由 KavaRef.logLevel 控制。

如果你有更高级的需求,你可以实现 KavaRefRuntime.Logger 来自定义自己的日志打印方式。

示例如下

kotlin
class MyLogger : KavaRefRuntime.Logger {

    // 在这里可以指定日志打印的标签
    override val tag = "MyLogger"

    override fun debug(msg: Any?, throwable: Throwable?) {
        // 在这里实现你的日志打印逻辑
    }

    override fun info(msg: Any?, throwable: Throwable?) {
        // 在这里实现你的日志打印逻辑
    }

    override fun warn(msg: Any?, throwable: Throwable?) {
        // 在这里实现你的日志打印逻辑
    }

    override fun error(msg: Any?, throwable: Throwable?) {
        // 在这里实现你的日志打印逻辑
    }
}

然后,将其设置到 KavaRef 上即可。

示例如下

kotlin
KavaRef.setLogger(MyLogger())

如果你需要将 KavaRef 的日志接入 SLF4J 等第三方日志框架,你可以在 这里 找到对应的接入方式。

进阶用法 ​

上述内容讲解的均为标准场景下的使用方法,如果你有更加细粒度的使用场景,你可以手动创建 KavaRef 的相关组件。

如果你不喜欢 Kotlin DSL 的写法,你可以手动创建链式调用。

示例如下

kotlin
// 假设这就是这个 Class 的实例
val test: Test
// 使用 KavaRef 调用并执行
Test::class.resolve()
    .method() // 条件开始
    .name("doTask")
    .parameters(String::class)
    .build() // 条件结束 (执行)
    .first()
    .of(test) // 设置实例
    .invoke("task_name")

你还可以手动创建任何过滤条件以实现在任何反射中复用它。

示例如下

kotlin
// 假设这就是这个 Class 的实例
val test: Test
// 手动创建 MethodCondition
val condition = MethodCondition<Test>()
condition.name = "doTask"
condition.parameters(String::class)
// 应用条件到反射对象
Test::class.resolve()
    .firstMethod(condition)
    .of(test) // 设置实例
    .invoke("task_name")

或者,你还可以手动完整地实现整个反射过程。

示例如下

kotlin
// 假设这就是这个 Class 的实例
val test: Test
// 手动创建 MethodCondition
val condition = MethodCondition<Test>()
condition.name = "doTask"
condition.parameters(String::class)
// 手动创建 MemberCondition.Configuration
val configuration = Test::class.java.createConfiguration(
    memberInstance = test, // 设置实例
    processorResolver = null, // 使用默认的解析器,可参考下方的 "自定义解析器"
    // 是否在超类中过滤,对使用此配置的全部过滤条件生效
    // NO 为不过滤,NORMAL 等同于 superclass(),INCLUDE_INTERFACES 等同于 superclass(interfaces = true)
    superclass = MemberCondition.Configuration.Superclass.NO,
    optional = MemberCondition.Configuration.Optional.NO // 配置可选条件
)
// 创建并开始过滤
val resolvers = condition.build(configuration)
// 获取第一个结果
val resolver = resolvers.first()
// 执行方法
resolver.invoke("task_name")

如果你对业务层逻辑有更高级的需求,你还可以使用 mergeWith 来合并多个过滤条件。

示例如下

kotlin
// 假设这就是这个 Class 的实例
val test: Test
// 手动创建 MethodCondition
// 创建第一个条件
val condition1 = MethodCondition<Test>()
condition1.name = "doTask"
// 创建第二个条件
val condition2 = MethodCondition<Test>()
condition2.parameters(String::class)
// 将 condition2 合并到 condition1 中
// 此时 condition1 的条件将包含 condition2 不为 null 的条件,
// condition1 中重复的条件将被 condition2 的条件覆盖
condition1.mergeWith(condition2)
// 你还可以使用 infix 语法
condition1 mergeWith condition2
// 使用 KavaRef 调用并执行
Test::class.resolve()
    .firstMethod(condition1)
    .of(test)
    .invoke("task_name")

特别注意

当 MemberCondition 已经设置 MemberCondition.Configuration 时将不再允许重复使用 build(...) 进行创建, 此时你需要使用 copy() 来复制并创建一份新的 MemberCondition。

同样地,InstanceAwareResolver 在通过 MemberCondition.Configuration.memberInstance 或 of(instance) 设置实例后也不允许重复设置新的实例, 此时你也需要使用 copy() 来复制并创建一份新的 InstanceAwareResolver。

自定义解析器 ​

KavaRef 使用默认的 Member 解析器进行过滤操作,如果你想实现自己的解析器,你可以自定义全局和每一个反射过程使用的解析器。

你可以继承于 MemberProccessor.Resolver 来实现自己的解析器。

示例如下

kotlin
class MyMemberProcessorResolver : MemberProcessor.Resolver() {

    override fun <T : Any> getDeclaredConstructors(declaringClass: Class<T>): List<Constructor<T>> {
        // 在这里拦截并实现你的构造方法过滤逻辑
        return super.getDeclaredConstructors(declaringClass)
    }

    override fun <T : Any> getDeclaredMethods(declaringClass: Class<T>): List<Method> {
        // 在这里拦截并实现你的方法过滤逻辑
        return super.getDeclaredMethods(declaringClass)
    }

    override fun <T : Any> getDeclaredFields(declaringClass: Class<T>): List<Field> {
        // 在这里拦截并实现你的字段过滤逻辑
        return super.getDeclaredFields(declaringClass)
    }
}

然后你可以将其设置到全局配置中。

示例如下

kotlin
MemberProcessor.globalResolver = MyMemberProcessorResolver()

或者,在每次反射过程中,你可以使用 MemberCondition.Configuration 来设置自定义解析器,或者使用链式调用设置解析器。

示例如下

kotlin
// 创建解析器
val myResolver = MyMemberProcessorResolver()
// 假设这就是这个 Class 的实例
val test: Test
// 使用 KavaRef 调用并执行
Test::class.resolve()
    // 设置自定义解析器
    .processor(myResolver)
    .firstMethod {
        name = "doTask"
        parameters(String::class)
    }.of(test).invoke("task_name")

小提示

你可以在 这里 找到一些公开维护的自定义解析器,定义在你的项目中即可使用。

自定义处理器 ​

KavaRef 使用默认的 Member 处理器进行调用与读写操作,如果你想实现自己的处理器,你可以自定义每一个 MemberResolver 使用的处理器。

你可以继承于 MethodResolver.Handler、ConstructorResolver.Handler 或 FieldResolver.Handler 来实现自己的处理器。

示例如下

kotlin
class MyMethodHandler : MethodResolver.Handler() {

    override fun invoke(method: Method, instance: Any?, args: Array<out Any?>): Any? {
        // 在这里拦截并实现你的方法调用逻辑
        return super.invoke(method, instance, args)
    }
}

class MyFieldHandler : FieldResolver.Handler() {

    override fun get(field: Field, instance: Any?): Any? {
        // 在这里拦截并实现你的字段读取逻辑
        return super.get(field, instance)
    }

    override fun set(field: Field, instance: Any?, value: Any?) {
        // 在这里拦截并实现你的字段写入逻辑
        super.set(field, instance, value)
    }
}

class MyConstructorHandler : ConstructorResolver.Handler() {

    override fun <T> newInstance(constructor: Constructor<T>, args: Array<out Any?>): T {
        // 在这里拦截并实现你的实例创建逻辑
        return super.newInstance(constructor, args)
    }
}

处理器在执行操作前会调用 requireAccessible(member) 将成员设置为可访问,如果你不需要这一步,可以重写它并且不调用 super。

示例如下

kotlin
class MyMethodHandler : MethodResolver.Handler() {

    override fun requireAccessible(member: Method) {
        // 不调用 super 以跳过设置可访问
    }
}

然后你可以使用 withHandler(handler) 来设置处理器。

示例如下

kotlin
// 假设这就是这个 Class 的实例
val test: Test
// 使用 KavaRef 调用并执行
Test::class.resolve()
    .firstMethod {
        name = "doTask"
        parameters(String::class)
    }.of(test)
    // 设置自定义处理器
    .withHandler(MyMethodHandler())
    .invoke("task_name")

注意

withHandler(handler) 不会修改当前的 MemberResolver,请使用它返回的 MemberResolver 进行后续操作。

小提示

你可以在 这里 找到一些公开维护的自定义处理器,定义在你的项目中即可使用。

关于缓存 ​

由于过滤条件的多样性,KavaRef 不直接提供缓存功能,根据每个开发者的实现方式不同,缓存的实现方式也会有所不同。

我们建议手动对过滤结果创建的 MemberResolver 实现缓存以提高性能并参考 进阶用法 拆分过滤条件以优化代码复用率。

特别注意

如果你使用了 val myResolver by lazy { ... } 来实现缓存,例如下方这样做。

示例如下

kotlin
val myResolver by lazy {
    Test::class.resolve()
        .firstMethod {
            name = "doTask"
            parameters(String::class)
        }
}

你在调用时可能会这样做。

示例如下

kotlin
// 假设这就是这个 Class 的实例
val test: Test
// 使用 KavaRef 调用并执行
myResolver.of(test).invoke("task_name")

请注意,由于 MemberResolver 已被缓存,在你每次引用它时调用的是同一个实例,而 MemberResolver 的实例对象不允许重复设置 (参考 进阶用法 下方的 “特别注意”), 所以直接这样调用会抛出异常,你需要改为以下形式。

示例如下

kotlin
// 假设这就是这个 Class 的实例
val test: Test
// 使用 KavaRef 调用并执行
myResolver.copy().of(test).invoke("task_name")

这样一来,你就可以在每次调用时复制一个新的 MemberResolver 实例而不需要重复反射过程,也不会抛出异常。

Java 用法 ​

KavaRef 不推荐直接在 Java 中使用,因为它的 API 设计是基于 Kotlin 的特性和语法糖。

如果你需要在 Java 中使用 KavaRef,你可以使用以下方式来实现。

示例如下

java
public class Main {

    public static void main(String[] args) {
        // 假设这就是这个 Class 的实例
        Test test;
        // 使用 KavaRef 调用并执行
        KavaRef.resolveClass(Test.class)
            .method()
            .name("doTask")
            .parameters(String.class)
            .build()
            .get(0)
            .of(test)
            .invoke("task_name");
        // 或者,使用实例创建 KavaRef 反射
        KavaRef.resolveObject(test)
            .method()
            .name("doTask")
            .parameters(String.class)
            .build()
            .get(0)
            .invoke("task_name");
    }
}

小提示

在 Java 中调用 KavaRef.resolveObject(...) 时,传入的 Class 对象同样会被作为实例,并且不会出现编译错误,反射一个类中的成员请使用 KavaRef.resolveClass(...)。

基于 Apache-2.0 许可发布