我在服务方法上添加了两个注释,编译完成后,我发现方法被编译成一个新的类文件,我反编译生成的类文件,发现@CompileStatic不能正常工作。
是正确的还是虫子的错误?
class FoobarService {
@grails.transaction.Transactional
@groovy.transform.CompileStatic
void foobar() {
....
}
}
答案 0 :(得分:2)
grails.transaction.Transactional
注释是传统Spring org.springframework.transaction.annotation.Transactional
注释的替代品。它具有相同的属性和功能,并且工作方式基本相同,但它避免了使用Spring注释的不幸副作用。
Spring注释触发创建带注释的类的运行时代理。 Spring使用CGLIB创建目标类的子类(通常是Grails服务),CGLIB代理的实例注册为Spring bean,而不是直接注册服务实例。代理将服务实例作为数据变量获取。
每个方法调用都在代理中被截获,在那里它根据事务设置执行任何检查和/或设置,例如,加入现有的事务,创建一个新的事务,抛出异常,因为一个事务尚未运行等等。一旦完成,就会调用你的真实方法。
但是,如果您使用不同的设置调用另一个带注释的方法(例如,第一个方法使用@Transactional
中的默认设置,但第二个应该在新的单独事务中运行,因为它使用@Transactional(propagation=REQUIRES_NEW)
进行注释)然后第二个注释设置将被忽略,因为您在代理正在拦截调用的服务的实际实例内“代理”下方。但它不能拦截那样的直接电话。
传统的解决方法是避免直接调用,而是在代理上进行调用。您不能(至少不方便)将服务bean注入其自身,但您可以访问应用程序上下文并以这种方式访问它。因此,在这种情况下您需要的呼叫将类似于
ctx.getBean('myService').otherMethod()
有效,但非常难看。
新的Grails注释工作方式不同。它在编译期间通过AST转换触发代码的重新处理。为每个带注释的方法创建第二个方法,并使用注释设置在GrailsTransactionTemplate
中运行来自真实方法的代码,在@CompileStatic
中运行代码。在那里,代码运行所需的事务设置,但由于每个方法都以这种方式重写,您不必担心代理和您从哪里调用方法 - 没有代理。
不幸的是,您会看到一个副作用 - 显然转换是以不保留{{1}}注释的方式发生的,因此代码以动态模式运行。对我来说听起来像个小虫。