AOP 与函数

60 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 23 天,点击查看活动详情

AOP 是真正全局概念(例如不直接影响代码逻辑的日志记录)的绝佳工具。但是,当 AOP 用于更多与业务相关的事情(如授权)时,就会出现问题。应用程序的这些方面必须在相关代码中清晰可见,以便开发人员在阅读相应的源代码时可以立即了解它们是否正确实现。基于 AOP 的框架通常通过使用方法注释来解决它:

@RequireRole(Role.*Admin*) *// clearly visible aspect*  
**fun** updateUserPermissions(…) {  
*// logic here  
*}

然而,从可读性的角度来看,它与使用函数而不是@RequireRole注释来解决同一问题的函数式方法没有太大区别:

**fun** updateUserPermissions(…) {  
requireRole(Role.*Admin*) *// throws SecurityException  
// logic here  
*}

此外,功能方法的优势在于它还可以扩展到更复杂的权限检查,例如在决定需要哪个用户角色之前分析方法参数。

交易等其他方面也是如此。不幸的是,在 Java 中用功能表示更复杂的概念既麻烦又不方便,这为 AOP 框架在 Java 生态系统中创造了人为的流行度。

但是,Kotlin 并非如此。在 Kotlin 中,使用 AOP 和注释代替类似 Java 的事务处理方法,如下所示:

@Transactional
fun updateUserPermissions(…) {
    // logic here
}

当以函数式方式重写时,它同样具有可读性和简洁性:

**fun** updateUserPermissions(…) = transactional **{  
***// logic here  
***}**

transactional这种函数式方法的优点是您始终可以在 IDE 中按Ctrl/Cmd+单击函数声明,然后立即查看它到底做了什么,这对于任何常用的 AOP 框架来说通常是不可能的。即使 IDE 插件提供了对方面源代码的导航,破译其逻辑也需要了解单独的丰富 API 和/或约定。

不幸的是,Kotlin 中基于注释的 AOP 的这种功能替代并不能立即扩展到当多个方面应用于同一功能时花括号和缩进开始堆积的情况:

**fun** updateUserPermissions(…) = logged **{  
**transactional **{  
***// logic here  
***}  
}**

解决方法是创建一个组合的高阶函数,以保持多个方面的使用位置看起来整洁:

**fun** updateUserPermissions(…) = loggedTransactional **{  
***// logic here  
***}**

函数式方法的另一个缺点是日志记录等方面需要访问方法参数。它们通常可通过特殊 API 在传统 AOP 框架中直接使用,但常用的 Kotlin 函数无法轻松访问它们。因此,为了以一种纯函数的方式实际表示现实生活中的日志记录方面,仍然需要编写大量的样板代码:

fun updateUserPermissions(params: Params) = 
    logged( "updateUserPermissions($ params )" ) {
 // logic here
 } 

当您确实需要在您的应用程序中全局且一致地使用 AOP 时,这种考虑仍然使 AOP 成为日志记录的首选工具,但我认为,考虑到 Kotlin 中可用的丰富功能抽象,将 AOP 用于授权和交易等方面是一种滥用. 函数确实能更好、更清晰地处理这些方面。

总而言之,我想说的是,进一步改进功能抽象以提供更好的 AOP 替代品可能是 Kotlin 语言未来发展的一个有前途的向量。基于 Java 的 AOP 框架通常是特定于 JVM 的并且被认为是一些不透明的魔法,而 Kotlin 函数抽象是真正跨平台的并且对用户是透明的。