Simple Aspects using Annotations in Grails

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:

  1. Create the UpdatesUser annotation
  2. Create a Spring bean for the ‘refreshAuthenticatedUser’ method which will perform the cache refresh
  3. Write a pointcut to match the ‘join points’ where the user is updated (ie the annotated methods on which the aspect should be applied)
  4. Mark up ‘refreshAuthenticatedUser’ with advice so it is called for annotated methods
  5. 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.

About these ads

3 thoughts on “Simple Aspects using Annotations in Grails

  1. 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.

  2. 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”.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s