Grails 的@Transactional 将禁用@CompileStatic 注释

Grails's @Transactional will disable the @CompileStatic annotation

我在服务方法上加了两个注解,编译后发现方法被编译成一个新的class文件,反编译生成的class文件发现@CompileStatic是未按预期工作。

是对还是 grails 的 bug?

class FoobarService {
    @grails.transaction.Transactional
    @groovy.transform.CompileStatic
    void foobar() {
        ....
    }
}

grails.transaction.Transactional 注释替代了传统的 Spring org.springframework.transaction.annotation.Transactional 注释。它具有相同的属性和特性,并且工作原理基本相同,但它避免了使用 Spring 注释的不幸副作用。

Spring 注释触发了注释 class 的 运行 时间代理的创建。 Spring 使用 CGLIB 创建目标 class 的子 class(通常是 Grails 服务)并且 CGLIB 代理的实例被注册为 Spring bean 而不是注册直接一个服务实例。代理获取您的服务实例作为数据变量。

每个方法调用都在代理中被拦截,它会根据交易设置进行任何检查 and/or 设置,例如加入一个现有事务,创建一个新事务,抛出一个异常,因为一个还没有 运行ning,等等。一旦完成,你的真实方法就会被调用。

但是,如果您使用不同的设置调用另一个带注释的方法(例如,第一个方法使用 @Transactional 中的默认设置,但第二个方法在新的单独事务中应该是 运行,因为它用 @Transactional(propagation=REQUIRES_NEW)) 然后第二个注释设置将被忽略,因为你是 "underneath" 代理,在代理拦截调用的服务的真实实例中。但是它不能拦截这样的直接调用。

传统的解决方法是避免直接调用,而是在代理上进行调用。您不能(至少不方便)将服务 bean 注入到自身中,但是您可以访问应用程序上下文并以这种方式访问​​它。所以在那种情况下你需要的电话是这样的

ctx.getBean('myService').otherMethod()

可以用,但是很丑。

不过,新的 Grails 注释的工作方式有所不同。它在编译期间通过 AST 转换触发代码的重新编写。为每个带注释的方法创建第二个方法,并将来自真实方法的代码移到其中,在 GrailsTransactionTemplate 中 运行 使用注释设置的代码。在那里,代码 运行s 具有所需的事务设置,但由于每个方法都以这种方式重写,因此您不必担心代理以及从何处调用方法 - 没有代理。

不幸的是,您看到了一个副作用 - 显然转换以不保留 @CompileStatic 注释的方式发生,因此代码 运行s 处于动态模式。对我来说听起来像是个错误。