This module provides abstractions to call user methods.
You can optionally install this dependency,
it requires Java 24+ and can be enabled with MethodAccessorsConfig.preferClassFileAccessors().
<dependencies>
<dependency>
<groupId>io.github.freya022</groupId>
<artifactId>BotCommands-method-accessors-classfile</artifactId>
<version>VERSION</version>
</dependency>
</dependencies>repositories {
mavenCentral()
}
dependencies {
implementation("io.github.freya022:BotCommands-method-accessors-classfile:VERSION")
}To use the latest, unreleased changes, see SNAPSHOTS.md.
This is the default implementation, as is what the framework originally used,
it only delegates to callSuspendBy with pairs of parameter -> value.
This newer implementation takes advantage of hidden classes and the Class-File API, which, for each function, generates a hidden class with instructions optimized to directly call the user method.
This allows for shorter stack traces in exceptions and the debugger, no InvocationTargetExceptions, and better performance.
Performance numbers from MethodAccessorBenchmark, baseline is a direct call:
| Function type | Baseline | ClassFile | kotlin-reflect |
|---|---|---|---|
| () -> String | 0.114 µs/op ± 0,003 |
0.115 µs/op ± 0,003 |
0.244 µs/op ± 0,056 |
| (a: String, b: Int = 42, c: Double = 3.14159) -> String | 0.179 µs/op ± 0,007 |
0.184 µs/op ± 0,010 |
0.525 µs/op ± 0,039 |
| suspend (a: String, b: Int = 42, c: Double = 3.14159) -> String | 0.183 µs/op ± 0,006 |
0.179 µs/op ± 0,007 |
0.531 µs/op ± 0,030 |
Note that each benchmark only use one accessor instance, in the real world there would be many more accessors, meaning that each virtual call becomes non-trivial (see megamorphic virtual calls) and thus slower, therefore this benchmark only shows:
- The custom classes can be as fast as direct calls
- The overhead of kotlin-reflect
Due to the nature of the use case, the accessors will not be as fast as the numbers above may make it look like, but it won't be slow either.
For example, a slash command handler will need to call the appropriate user-defined method, and each method has its own accessor, meaning that, at the call site (where the method is called), the JVM has no clue which accessor it has to call!
Consequently, it will have to determine which implementation of the accessor it has to call (remember, 1 method = 1 implementation). Also note that this issue is due to the use case, a direct interface call, a custom accessor or a reflection call, would all have the same slowdown.

