Join Points
A JoinPoint represents the context of an intercepted function call. AspectK generates a
JoinPoint instance at each call site and passes it to the matching advice functions.
JoinPoint Interface
interface JoinPoint {
val target: Any? // receiver object (null for top-level functions)
val signature: MethodSignature // compile-time method metadata
val args: List<Any?> // runtime arguments in declaration order
}
target — The Receiver
target is the object on which the intercepted method is called:
class UserService {
@Logged
fun getUser(id: String): User { ... }
}
@Aspect
object LoggingAspect {
@Before(target = [Logged::class])
fun log(jp: JoinPoint) {
val service = jp.target as? UserService // the UserService instance
println("Called on: $service")
}
}
For top-level functions, target is null:
signature — Method Metadata
MethodSignature provides compile-time metadata about the intercepted function:
data class MethodSignature(
val methodName: String, // simple function name
val annotations: List<AnnotationInfo>, // annotations on the function
val parameter: List<MethodParameter>, // parameter descriptors
val returnType: KClass<*>, // erased return type
val returnTypeName: String, // fully-qualified return type name
)
Example
@Before(target = [Logged::class])
fun inspect(jp: JoinPoint) {
val sig = jp.signature
println("name : ${sig.methodName}")
println("returnType : ${sig.returnTypeName}")
println("parameters : ${sig.parameter.map { "${it.name}: ${it.typeName}" }}")
println("annotations: ${sig.annotations.map { it.typeName }}")
}
Generic Type Erasure
When the return type is a generic type parameter (e.g., T), returnType and
returnTypeName are resolved to the upper bound at compile time:
fun <T> identity(value: T): T = value
// sig.returnType == Any::class
// sig.returnTypeName == "kotlin.Any"
args — Runtime Arguments
args is a List<Any?> of the arguments passed to the intercepted function,
in declaration order:
@Logged
fun transfer(fromId: String, toId: String, amount: Double) { ... }
@Before(target = [Logged::class])
fun log(jp: JoinPoint) {
val fromId = jp.args[0] as String // "acc-001"
val toId = jp.args[1] as String // "acc-002"
val amount = jp.args[2] as Double // 150.0
}
Nullable Arguments
When a parameter is declared as nullable, the corresponding args element may be null:
Check MethodParameter.isNullable to determine if null is expected:
jp.signature.parameter.zip(jp.args).forEach { (param, value) ->
if (param.isNullable || value != null) {
println("${param.name} = $value")
}
}
AnnotationInfo — Annotation Details
Annotations on functions and parameters are exposed as AnnotationInfo:
data class AnnotationInfo(
val type: KClass<out Annotation>, // annotation class
val typeName: String, // FQN string
val args: List<Any?>, // explicitly provided argument values
val parameterNames: List<String>, // corresponding parameter names
)
jp.signature.annotations.forEach { info ->
println("@${info.typeName}")
info.parameterNames.zip(info.args).forEach { (name, value) ->
println(" $name = $value")
}
}
Note
Only arguments explicitly provided in source appear in args. Arguments using
default values are omitted. Use parameterNames to identify which arguments are present.
Supported Function Types
AspectK can intercept all of the following function kinds. The target and args layout
varies by function type:
Class member function
class UserService {
@Logged
fun getUser(id: String): User { ... }
}
// jp.target → UserService instance
// jp.args → [id]
Top-level function
Extension function
The extension receiver is prepended to args; target is null.
@Logged
fun String.process(suffix: String) { ... }
// jp.target → null
// jp.args → [receiverString, suffix]
suspend function
Suspension machinery is transparent — args contains only the declared parameters.
@Logged
suspend fun fetchData(url: String): String { ... }
// jp.target → receiver instance (or null for top-level)
// jp.args → [url]
Property getter
The receiver object is passed as args[0]; target is null.
class Config {
val name: String
@Logged get() = "aspectk"
}
// jp.target → null
// jp.args → [Config instance]
Property setter
The receiver is args[0] and the incoming value is args[1]; target is null.
class Config {
var name: String = ""
@Logged set(value) { field = value }
}
// jp.target → null
// jp.args → [Config instance, newValue]
expect/actual function
Advice is woven into the actual declaration on each platform; behaviour mirrors a
top-level function.