如何在Kotlin中实现@RestrictCallers注解以限制方法调用者?
实现@RestrictCallers注解的两种方案
你定义的@RestrictCallers注解本身只是一个标记,Kotlin不会自动强制执行调用限制,需要配合运行时校验或编译期检查来实现需求。以下是两种具体实现方式:
一、运行时校验(快速实现)
通过获取调用栈,在方法执行前检查调用者是否属于注解指定的类,非法调用直接抛出异常。
修改后的代码实现
@Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) annotation class RestrictCallers(val caller: KClass<out Any>) class DlgManager { fun dismiss(dlg: Dialogue) { dlg.onDismiss() } } class Dialogue { @RestrictCallers(DlgManager::class) fun onDismiss() { // 1. 获取当前方法的注解 val restrictAnnotation = this::onDismiss.findAnnotation<RestrictCallers>() ?: throw IllegalStateException("RestrictCallers annotation not found") val allowedCallerClass = restrictAnnotation.caller.java // 2. 遍历调用栈,找到实际调用者 val stackTrace = Thread.currentThread().stackTrace val validCallingFrame = stackTrace.firstOrNull { frame -> // 跳过当前类、Thread类的栈帧,找到真正的调用者 !frame.className.startsWith(this::class.java.name) && !frame.className.startsWith("java.lang.Thread") } ?: throw IllegalAccessException("Cannot determine caller") // 3. 校验调用者是否合法 val callingClass = Class.forName(validCallingFrame.className) if (!allowedCallerClass.isAssignableFrom(callingClass)) { throw IllegalAccessException( "Method onDismiss() can only be called by ${allowedCallerClass.simpleName}, " + "but was called by ${callingClass.simpleName}" ) } // 4. 原方法业务逻辑 println("Dialogue dismissed") } }
优缺点
- 优点:实现简单,无需额外工具或插件,适合快速验证需求。
- 缺点:有运行时性能开销,且调用栈存在被篡改的可能,安全性并非绝对。
二、编译期检查(推荐方案)
通过自定义Kotlin Lint规则或编译器插件,在编译阶段直接拦截非法调用,从根源上阻止错误代码生成。这里以Kotlin Lint规则为例说明:
核心实现逻辑
- 自定义Lint检测器,扫描所有方法调用。
- 识别带有
@RestrictCallers注解的方法。 - 检查调用该方法的类是否属于注解指定的允许类。
- 若非法调用,直接抛出编译错误。
Lint检测器代码示例
import com.android.tools.lint.detector.api.* import com.intellij.psi.PsiMethod import org.jetbrains.uast.UCallExpression class RestrictCallersDetector : Detector(), SourceCodeScanner { companion object { val ISSUE = Issue.create( id = "RestrictedMethodInvocation", briefDescription = "Invoking a method restricted to specific callers", explanation = "This method can only be called by the class specified in @RestrictCallers.", category = Category.CORRECTNESS, priority = 6, severity = Severity.ERROR, implementation = Implementation( RestrictCallersDetector::class.java, Scope.JAVA_FILE_SCOPE ) ) } // 指定要扫描的方法(可扩展为扫描所有带@RestrictCallers的方法) override fun getApplicableMethodNames(): List<String> { return listOf("onDismiss") } override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { // 获取方法上的@RestrictCallers注解 val restrictAnnotation = method.getAnnotation("com.yourpackage.RestrictCallers") ?: return // 解析注解中允许的调用类 val allowedCaller = restrictAnnotation.findAttributeValue("caller")?.text ?.removeSurrounding("class ", "::class") ?: return // 获取当前调用所在的类 val callingClassName = context.evaluatedOwner?.className ?: return // 校验调用者是否合法 if (callingClassName != allowedCaller) { context.report( ISSUE, node, context.getLocation(node), "Method `${method.name}` can only be called by $allowedCaller" ) } } }
集成方式
将上述检测器注册到项目的Lint配置中,编译时Lint会自动扫描代码,非法调用onDismiss()的代码会直接报错,无法通过编译。
优缺点
- 优点:编译期拦截,无运行时开销,安全性高,能从根源上避免非法调用。
- 缺点:需要编写Lint规则或编译器插件,实现复杂度较高。
替代方案(无需注解)
如果不想实现注解,也可以通过Kotlin的访问控制简化逻辑:
- 将
Dialogue的onDismiss()设为internal,并把DlgManager放在同一个模块内,其他模块无法调用。但同一模块内的其他类仍能调用,适合模块内的简单限制。
内容的提问来源于stack exchange,提问作者digory doo
相关产品推荐
相关产品推荐

