kavaref-core

Maven CentralMaven metadata URL

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

配置依赖

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

SweetDependency (推荐)

在你的项目 SweetDependency 配置文件中添加依赖。

libraries:
  com.highcapable.kavaref:
    kavaref-core:
      version: +

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

implementation(com.highcapable.kavaref.kavaref.core)

Version Catalog

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

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

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

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

implementation(libs.kavaref.core)

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

传统方式

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

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

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

功能介绍

你可以 点击这里在新窗口中打开 查看 KDoc。

基本用法

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

关系图如下。

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

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

package com.demo;

public class BaseTest {

    public BaseTest() {
        // ...
    }

    private void doBaseTask(String taskName) {
        // ...
    }
}
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() {
        // ...
    }
}
public class Box<T> {

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

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

示例如下

// 假设这就是这个 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() 的调用链可能来起来会比较繁琐,这个时候就有以下简化方案。

示例如下

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

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

示例如下

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

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

示例如下

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

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

示例如下

val test = Test::class.resolve()
    .firstConstructor {
        // 对于零参构造方法,可以使用以下条件过滤
        // 它等价于 parameterCount = 0
        emptyParameters()
    }.create() // 创建一个新的 Test 实例

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

示例如下

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

小提示

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

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

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

注意

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

特别注意

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

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

示例如下

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 来填充你不想填写的方法参数类型。

示例如下

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

注意

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

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

自由条件

MemberCondition 中,nametypeparameterCount 等条件都可以使用 Kotlin lambda 特性创建自由过滤条件。

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

示例如下

// 假设这就是这个 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 支持添加泛型过滤条件,你可以使用 TypeMatcher 提供的相关功能来实现。

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

示例如下

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

在超类过滤

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

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

示例如下

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

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

小提示

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

特别注意

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

更多条件

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

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

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

示例如下

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

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

示例如下

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

异常处理

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

示例如下

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

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

示例如下

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

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

示例如下

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

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

特别注意

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

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

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

示例如下

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

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

示例如下

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 上即可。

示例如下

KavaRef.setLogger(MyLogger())

进阶用法

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

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

示例如下

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

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

示例如下

// 假设这就是这个 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")

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

示例如下

// 假设这就是这个 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, // 使用默认的解析器,可参考下方的 "自定义解析器"
    superclass = false, // 是否在超类中过滤
    optional = MemberCondition.Configuration.Optional.NO // 配置可选条件
)
// 创建并开始过滤
val resolvers = condition.build(configuration)
// 获取第一个结果
val resolver = resolvers.first()
// 执行方法
resolver.invoke("task_name")

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

示例如下

// 假设这就是这个 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.memberInstanceof(instance) 设置实例后也不允许重复设置新的实例, 此时你也需要使用 copy() 来复制并创建一份新的 InstanceAwareResolver

自定义解析器

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

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

示例如下

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)
    }
}

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

示例如下

MemberProcessor.globalResolver = MyMemberProcessorResolver()

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

示例如下

// 创建解析器
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 不直接提供缓存功能,根据每个开发者的实现方式不同,缓存的实现方式也会有所不同。

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

特别注意

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

示例如下

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

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

示例如下

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

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

示例如下

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

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

Java 用法

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

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

示例如下

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");
    }
}