Scala:围绕建议或Python装饰器实现Java的AspectJ

时间:2011-05-14 14:23:15

标签: java python scala aop aspectj

我一直在为我的创业公司广泛使用Java + AspectJ。我想切换到Scala,但我有一个共同的设计模式,我不确定在Scala中实现的最佳方式。

我们的大量应用程序使用注释作为标记使用AspectJ切入点。 这与Python的装饰器和blogged about it here非常相似。

我尝试在Scala中使用此技术,但遇到了AspectJ + Scala的问题。 即使我确实让它工作,似乎unScala喜欢。

我已经看到一些项目做了一些名称封闭魔术(我认为这就是他们正在做的事情)。

替换@Transaction的示例:

transaction {
// code in here.
}

我不得不说,虽然我更喜欢注释,因为它似乎更具说明性。 以声明方式“装饰”代码块的Scala方式是什么?

3 个答案:

答案 0 :(得分:12)

顺便说一句,我正在Scala Days 2011 on the same topic发表演讲。核心理念与Kim和Dean的榜样相同。然而,当谈到全方位的横切关注点时,相似性和差异会变得更加微妙。

在光谱的一端,有一些非真正横切的问题,比如缓存。当宿主语言不支持高阶函数(例如,Java)时,将关注点作为一个方面实现变得有吸引力。例如,使用AspectJ和注释方法,您可以说:

    @Cacheable(keyScript="#account.id")
    public double getNetWorth(Account account) {
       ... expensive computation
    }

但是在Scala中使用高阶函数,你可以这样做:

    def getNetWorth(account: Account) : Double = {
        cacheable(keyScript=account.id) {
          ... expensive computation
        }
    }   

Scala方法要好得多,因为:

  • 缓存不太可能广泛适用。例如,类中的所有方法或包中所有类的所有公共方法都不可能是可缓存的。即使存在这种情况,keyScript也不可能以通用形式相同或易于表达。
  • AspectJ方法使用注释作为拐杖来提供体面的实现。使用Scala中的高阶函数,意图直接表达。
  • AspectJ方法需要使用外部语言(例如OGNL或Spring Expression Language)来计算密钥。使用Scala,您可以使用宿主语言。

在中间,常见的横切涉及事务管理和安全性。从表面上看,它们看起来很像缓存。但是,我们在实践中发现将这些功能应用于类的所有方法(具有通用子选择,例如具有公共访问权限的通用子选择)或标记有注释(例如@Service)的所有类的所有方法都是常见的。如果是这种情况,AspectJ方法最终会变得更优越,因为它提供了一种将功能应用于更高级别功能的方法。当类级注释完成时,您不再需要使用transactional {}secured {}包围每个方法。对于类似安全性的问题,AspectJ方法允许更简单的方法来执行安全审核。

另一方面是横切关注点,例如跟踪,分析,监控,策略实施,审计,某些形式的并发控制(例如Swing的/ SWT / Android UI线程调度)这些非常适合通过切入点选择(通常没有注释)。单独使用高阶函数以一致的方式执行相同操作非常困难。

有更多的语义细微差别,但最重要的是,当您发现注释每个方法以应用横切关注点时,高阶函数可能是更好的方法。对于其他人来说,将Scala与AspectJ一起使用可能会提供一致且紧凑的解决方案。

P.S。我最近没有在Eclipse中尝试过AspectJ + Scala(因为Eclipse中的Scala最近才开始工作)。但是在http://lampsvn.epfl.ch/trac/scala/ticket/4214修复后,使用Maven的外部构建工作正常。

答案 1 :(得分:8)

scala方式将是

def transaction(f: =>Unit) = {
  println("start transaction")
  f
  println("end transaction")
}

transaction {
  println("inside transaction")
}

打印

start transaction
inside transaction
end transaction

答案 2 :(得分:5)

使用事务方法方法与注释相比还有其他优点。您可以添加catch和finally子句以确保正确的资源清理。延伸Kim的例子:

def transaction(f: =>Unit) = {
  println("start transaction")
  try {
    f
    println("end successful transaction")
  } catch {
    case ex => 
      // rollback?
      println("end failed transaction")
  } finally {
      // cleanup?
      println("end cleanup")
  }      
}

transaction {
  println("inside transaction")
}

您也可以在方法体内调用事务,而不能在方法内部注释块。当然,您可以使用注释使内部块进行另一个方法调用。

从我的Java时代开始,我理解注释和XML配置文件的吸引力,但是现在,我更喜欢将所有内容写成“正常”代码,因为它具有一致性和更强的表达能力。当我调用需要它们的Java库时,我只使用注释。此外,如果您使代码尽可能“功能”,那么一切都是声明性的! ;)