Saturday, May 11, 2013

Spring AOP: Ordering Aspects

Sometimes we may want to apply multiple aspects to the same pointcut when using Spring AOP, keeping each aspect responsible for a single activity or concern. Below we'll discuss a business example of why we may want to order more than one aspects and how to ensure that ordering using Spring. Again, we'll be writing our code in Groovy to keep things simple.

To start our example, let's say we have a service method that takes a Purchase and returns a Shipment:

interface PurchaseProcessor {
    Shipment process(Purchase)
}

Our first use of AOP may be recording some information about the Purchase for statistics purpose. Since statistics keeping is a separate concern from actually processing the Purchase, it serves as a perfect candidate for an aspect. For the purposes of this example, we will use an aspect that provides around advice instead of an alternative like before advice:

class StatRecordingAspect {

    StatRecorder statRecorder

    Object around(ProceedingJoinPoint joinPoint) {
        Purchase purchase = joinPoint.args[0]
        statRecorder.record purchase
        joinPoint.proceed()
    }
}

Later on, we decide we want to inform an internal process via JMS when the processed Shipment is on its way out the door. As the sending of the information could be considered another concern separate from the actual processing of the Purchase we decide it to should be done in an aspect. Additionally, this notification requires information from the statistics from the Purchase:

class NotificationAspect {

    JmsShipmentNotifier notifier
    StatRecorder statRecorder

    Object around(ProceedingJoinPoint joinPoint) {
        Purchase purchase = joinPoint.args[0]
        Shipment shipment = (Shipment)joinPoint.proceed()
        notifier.notifyAbout shipment, statRecord.statsFor(purchase)
        shipment
    }
}

The above business case requires that the stats about the Purchase be recorded before the notification. If we target both aspects at the PurchaseProcessor method via the same pointcut, how do we guarantee that the stats are recorded first?

We could opt to combine the two aspects into one, but it could be argued then the combined aspect would have one more than responsibility, record stats and sending out notifications, and have more than one concern.

A solution provided by Spring AOP is to have each aspect implement the Ordered interface provided by spring-core (Spring also provides an @Order annotation as an alternative to implemented the Ordered interface.):

.
class StatRecordingAspect implements Ordered { //...

    Object around(ProceedingJoinPoint joinPoint) { //... }

    @Override
    int getOrder() {
        0
    }
}

class NotificationAspect implements Ordered { //...

    Object around(ProceedingJoinPoint joinPoint) { //... }

    @Override
    int getOrder() {
        1
    }
}

With the above implementations, the StatRecordingAspect would be executed first as it is the aspect with the lowest order.

If the above makes you a little uneasy because the values returned are not linked to each other in any way, the values seem arbitrary, you could relate the orders to each other with an enumeration:

enum AspectOrder {

    STAT_RECORDING(0),
    NOTIFICATION(1)

    final int value

    AspectOrder(int value) {
        this.value = value
    }

    int value() {
        value
    }
}


class StatRecordingAspect implements Ordered { //...

    Object around(ProceedingJoinPoint joinPoint) { //... }

    @Override
    int getOrder() {
        AspectOrder.STAT_RECORDING.value
    }
}

class NotificationAspect implements Ordered { //...

    Object around(ProceedingJoinPoint joinPoint) { //... }

    @Override
    int getOrder() {
        AspectOrder.NOTIFICATION.value
    }
}

Now in your unit tests you can compare the order value of one aspect to the others when testing the getOrder() method, ensuring they would in practice be ordered as you would expect.

Loading Classpath Resources from a Static Context

Recently, I had to figure out how to load classpath resources from within a static method in a test class. I've been doing it from a non-static method forever, but had to do some searching to figure out how to do it from a static method.

For example to build a scenario where one might want to do this, let's take a generic Record class that is used by many of our classes under test. As JSON is the input method for records from the user of our system, it might make sense to store our records to use during our tests as JSON test resources.

During our tests, we can load the record JSON from a test resource on the classpath with a test helper class like the below. The Groovy test class uses the ObjectMapper class from Jackson:

class RecordBuilder {
    def recordFor(resource) {
        new ObjectMapper().readValue textOf(resource), Record.class
    }
    def textOf(resource) {
        getClass().getClassLoader().getResourceAsStream(resource).text
    }
}

One annoyance with using the above class in our test classes is that each test class would need to create or be given an instance of RecordBuilder to do its work. An alternative is to make the record-building method static, which should be ok since the normal concerns regarding static methods, pro or con, should not apply in a testing scenario as tests are usually executed one at at time.

The problem with making the recordFor method static is the approach to reading the resource from the classpath no longer works, and the solution may not be apparent to those who have not tried to do something similar before. Below includes the answer I was able to find, referring to the RecordBuilder class itself:

class RecordBuilder {
    static def recordFor(resource) {
        new ObjectMapper().readValue textOf(resource), Record.class
    }
    static def textOf(resource) {
        RecordBuilder.class.getClassLoader().getResourceAsStream(resource).text
    }
}

I guess this was a rather long-winded way to show how to load classpath resources from a static context, but I hope it will do. Thanks.