SpelIdempotentKeyResolver.kt
package io.github.lishangbu.avalon.idempotent.key
import io.github.lishangbu.avalon.idempotent.annotation.Idempotent
import io.github.lishangbu.avalon.idempotent.properties.IdempotentProperties
import org.aspectj.lang.ProceedingJoinPoint
import org.springframework.context.expression.MethodBasedEvaluationContext
import org.springframework.core.DefaultParameterNameDiscoverer
import org.springframework.expression.spel.standard.SpelExpressionParser
import org.springframework.expression.spel.support.StandardEvaluationContext
import org.springframework.web.context.request.RequestContextHolder
import org.springframework.web.context.request.ServletRequestAttributes
import java.lang.reflect.Method
/**
* Resolves idempotent keys from SpEL expressions.
*/
class SpelIdempotentKeyResolver(
private val properties: IdempotentProperties,
) : IdempotentKeyResolver {
private val parser = SpelExpressionParser()
private val parameterNameDiscoverer = DefaultParameterNameDiscoverer()
override fun resolve(
joinPoint: ProceedingJoinPoint,
method: Method,
annotation: Idempotent,
): String {
if (annotation.key.isBlank()) {
return resolveFromRequestHeader(method)
}
val context =
MethodBasedEvaluationContext(
joinPoint.target,
method,
joinPoint.args,
parameterNameDiscoverer,
).apply {
setVariable("method", method)
setVariable("target", joinPoint.target)
}
val value = parser.parseExpression(annotation.key).getValue(context as StandardEvaluationContext)
return value?.toString()?.trim()?.takeIf { it.isNotEmpty() }
?: error("Resolved idempotent key is blank for method ${method.declaringClass.name}.${method.name}.")
}
private fun resolveFromRequestHeader(method: Method): String {
val requestAttributes = RequestContextHolder.getRequestAttributes() as? ServletRequestAttributes
val headerValue =
requestAttributes
?.request
?.getHeader(properties.headerName)
?.trim()
?.takeIf { it.isNotEmpty() }
return headerValue
?: error(
"No idempotent key expression was configured for ${method.declaringClass.name}.${method.name}, and request header '${properties.headerName}' is missing.",
)
}
}