简体   繁体   中英

Grails's @Transactional will disable the @CompileStatic annotation

I add two annotations on the service method, after compiled, I found the method were compiled to a new class file, and I decompiled the generated class files and found the @CompileStatic were not work as wished.

Is is right or a bug of grails?

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

The grails.transaction.Transactional annotation is a replacement for the traditional Spring org.springframework.transaction.annotation.Transactional annotation. It has the same attributes and features and works essentially the same, but it avoids an unfortunate side effect of using the Spring annotation.

The Spring annotation triggers the creation of a runtime proxy of the annotated class. Spring uses CGLIB to create a subclass of the target class (typically a Grails service) and an instance of the CGLIB proxy is registered as the Spring bean instead of registering a service instance directly. The proxy gets an instance of your service as a data variable.

Each method call is intercepted in the proxy, where it does whatever checks and/or setup is required based on the transaction settings, eg joining an existing transaction, creating a new one, throwing an exception because one isn't already running, etc. Once that's done, your real method is called.

But if you call another annotated method with different settings (eg the first method uses the default settings from @Transactional but the second should be run in a new separate transaction because it's annotated with @Transactional(propagation=REQUIRES_NEW) ) then the second annotations settings will be ignored because you're "underneath" the proxy , inside the real instance of your service that the proxy is intercepting calls to. But it can't intercept direct calls like that.

The traditional workaround for this is to avoid direct calls and instead make the call on the proxy. You can't (at least not conveniently) inject the service bean into itself, but you can access the application context and access it that way. So the call that you would need in that situation would be something like

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

which works, but is pretty ugly.

The new Grails annotation works differently though. It triggers a reworking of the code via an AST transformation during compilation. A second method is created for each annotated method, and the code from the real method is moved inside there, in a GrailsTransactionTemplate that runs the code using the annotations settings. Once there, the code runs with the required transaction settings, but since every method is rewritten in this way, you don't have to worry about the proxy and where you're calling the methods from - there is no proxy.

Unfortunately there's a side effect that you're seeing - apparently the transformation happens in a way that isn't preserving the @CompileStatic annotation, so the code runs in dynamic mode. Sounds like a bug to me.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM