Aspect Oriented Programming is a concept which will be familiar to users of the Spring Framework as one of its core features. However, the details of how to get AOP working in Grails appear thin on the ground, so in this post I will show how to set up a simple aspect then configure and apply it using attributes. I will assume some familiarity with Spring AOP so I won’t explain the terminology or general concepts since they are exactly the same in Java as they are in Grails.
Building an Aspect Around Spring Security
In my last post I showed how to update the logged-in user domain object when using the Spring Security plugin – whenever the user domain object is updated the security context also needs to be updated as the plugin caches the logged in user. A nice way to implement this cache refresh would be to annotate those methods which update the user domain object; given a UserService with an updateUser method, I want to be able to annotate that method so that the cached domain object is refreshed:
public class UserService { @UpdatesUser User updateUser(user) { println "updateUser" } }
To implement this aspect I will follow these 5 steps:
- Create the UpdatesUser annotation
- Create a Spring bean for the ‘refreshAuthenticatedUser’ method which will perform the cache refresh
- Write a pointcut to match the ‘join points’ where the user is updated (ie the annotated methods on which the aspect should be applied)
- Mark up ‘refreshAuthenticatedUser’ with advice so it is called for annotated methods
- Configure the Spring container to use the aspect
Aspects can be written in Groovy or Java but since this is a Grails post I’ll show a pure Groovy/Grails example using @AspectJ annotations
Creating the Annotation
The noteworthy thing about the annotation is that it needs to be retained at runtime. Otherwise it’s very simple and can live in the src/groovy directory:
package example import java.lang.annotation.ElementType import java.lang.annotation.Target import java.lang.annotation.RetentionPolicy import java.lang.annotation.Retention @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface UpdatesUser { }
Creating a Spring Bean for the Aspect
Taking the code from my last post to refresh the user cache I can create an aspect using markup:
package example import org.springframework.security.context.SecurityContextHolder import org.springframework.security.providers.UsernamePasswordAuthenticationToken import org.codehaus.groovy.grails.plugins.springsecurity.GrailsUserImpl import org.springframework.security.GrantedAuthorityImpl import org.springframework.security.GrantedAuthority @Aspect @Component("refreshUserAspect") public class RefreshUserAspect { public void refreshAuthenticatedUser(User user){ GrantedAuthority[] auths = user.authorities.collect { new GrantedAuthorityImpl(it.authority) } def grailsUser = new GrailsUserImpl( user.username, "", user.enabled, true, true, true, auths, user) def authToken = new UsernamePasswordAuthenticationToken(grailsUser, "", auths) SecurityContextHolder.context.authentication = authToken } }
So the only additions to the code are the annotations:
- @Aspect to allow the framework to auto-create this class as an aspect
- @Component tells the framework that this is a Spring bean with the name “refreshUserAspect”
Defining a Pointcut
The pointcut definition tells the framework when the refreshAuthenticatedUser method should be called, in this case it’s whenever the UpdatesUser annotation is present. The definition will live in the same class as the Aspect:
@Aspect @Component("refreshUserAspect") public class RefreshUserAspect { @Pointcut("@annotation(example.UpdatesUser)") public void userUpdatingOperation() { } public void refreshAuthenticatedUser(User user){ ... } }
The pointcut is just an annotated empty method and the UpdatesUser annotation is referenced by its fully qualified class name in the pointcut annotation.
Declaring the Advice
An advice declaration relates a pointcut to a method and in this case must also inject the user object into the method.
It looks like this:
@AfterReturning(pointcut="example.RefreshUserAspect.userUpdatingOperation()", returning="user") public void refreshAuthenticatedUser(User user){ ... }
It’s saying:
- @AfterReturning – call this method on successful execution of methods
- pointcut=”example.RefreshUserAspect.userUpdatingOperation” – attach this advice to methods matched by the pointcut defined on the userUpdatingOperation method of this class
- returning=”user” – use the return value of the matched method for the ‘user’ argument of this advice
Configuring the Spring Container
In order for Spring to find the @AspectJ annotated aspect it’s necessary to define an AnnotationAwareAspectJAutoProxyCreator. It shouldn’t be needed since Grails 1.2.x but Grails 1.2.1 has a few problems with its aspect support which I will discuss later.
Define the AnnotationAwareAspectJAutoProxyCreator in Resources.groovy:
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator import example.aop.SimpleAspect beans = { autoProxyCreator(AnnotationAwareAspectJAutoProxyCreator) { proxyTargetClass = true } }
So the final Aspect looks like this:
package example import org.springframework.security.context.SecurityContextHolder import org.springframework.security.providers.UsernamePasswordAuthenticationToken import org.codehaus.groovy.grails.plugins.springsecurity.GrailsUserImpl import org.springframework.security.GrantedAuthorityImpl import org.springframework.security.GrantedAuthority @Aspect @Component("refreshUserAspect") public class RefreshUserAspect { @Pointcut("@annotation(example.UpdatesUser)") public void userUpdatingOperation() { } @AfterReturning(pointcut="example.RefreshUserAspect.userUpdatingOperation()", returning="user") public void refreshAuthenticatedUser(User user){ GrantedAuthority[] auths = user.authorities.collect { new GrantedAuthorityImpl(it.authority) } def grailsUser = new GrailsUserImpl( user.username, "", user.enabled, true, true, true, auths, user) def authToken = new UsernamePasswordAuthenticationToken(grailsUser, "", auths) SecurityContextHolder.context.authentication = authToken } }
Now if the updateUser method in the UserService is annotated:
public class UserService { @UpdatesUser User updateUser(user) { println "updateUser" } }
the spring security user object will be refreshed thanks to a call to the aspect’s @AfterReturning advice. Although this example is quite simple there’s not much more that is Grails-specific left to know when creating aspects.
Problems in Grails 1.2.1
There are a number of issues with aspect support in Grails 1.2.1 which make working with aspects difficult. Most should be resolved in the 1.2.2 release but I’ll mention some of them here since they make certain operations impossible.
Repeat calls to the same advice
Although the annotation-based pointcuts I’ve described here work perfectly, other types of pointcuts can cause problems. For example, defining a pointcut as:
@Pointcut("execution(* example.UserService.updateUser(..))")
should match the service method and call the advice just the same as the annotation, but what actually happens is that the advice is called more than once for every call to updateUser.
I haven’t tried all types of pointcut definition, but be aware that other types may suffer from this problem.
Service methods subject to advice cannot be edited on-the-fly
Another unfortunate problem is that if a service has a method which is subject to an aspect, changing that service in any way will cause it to be recompiled and future calls to that service method will fail with a CGLIB error when running the application in development (grails run-app):
java.lang.ClassCastException: example.SimpleService$$EnhancerByCGLIB$$735effdb cannot be cast to example.UserService
I’m not sure if this is likely to be fixed in the next version of grails or not.
@AspectJ annotated aspects are not auto-discovered
It should be possible to use beans with @AspectJ annotations without creating an AnnotationAwareAspectJAutoProxyCreator, however Grails doesn’t find the annotated classes so the auto-proxy creator must be manually defined in the Spring resources as described earlier in this post. This should be fixed in Grails 1.2.2.
The Future
I’ll update this post with more information after the next release of Grails, when hopefully some of the problems are resolved, until then I hope this serves as a useful example.
Well covered! Have you considered trying a “pure Spring AOP” approach? Seems like you could make use the Spring DSL to define and configure the aspect.
Cheers,
Dan
Thanks Dan, I tried the DSL but that’s when I hit the Grails 1.2.1 problem – my advice got called multiple times. I’ll try again with 1.3 when I have the time and compare the DSL with this approach, I’m sure it would be much quicker.
Seams like these problems is not repaired still in 1.3.7. In Graisl version 1.3.7 I’m having same issues “Repeat calls to the same advice” and “Service methods subject to advice cannot be edited on-the-fly”.