gargoyle image Collected works and current thoughts

Spring AOP

Quick overview of getting Spring Aop up and running

Introduction

Many years ago I worked with AspectJ on a project to improve a part of an aging system. I wrote up some tutorials on that here.

Fast forward 5 or 6 years and on the project we’re working on we have a few places where AOP would be handy. One is metrics/logging related. We are using Spring 3 and component scanning, so I figured using Spring AOP is an obvious choice. We are already using it (badly) in another part of the system, so we are not introducing any new technology into our current stack.

This blog is about mechanics, rather than concepts. If you want concepts, I can recommend the aforementioned tutorials or AspectJ in Action. I will make some observations about differences between “raw” AspectJ and Spring AOP below.

What you need to do

You can find the latest version of all of this code on github.

This example uses Spring 3.1, though 2.5 or greater should work fine. One thing I like to see when reviewing code examples is a list of the required dependencies. I started with an empty directory and created a maven project from scratch rather than using an archetype. Instead of filling this page with all of those details, if are so inclined, review the pom here.

Enabling

To make this easy/possible, we need to inform spring that we are using AOP. In addition, this example uses auto-discovery of spring beans. To make this happen, I use the following applicationContext.xml file:

applicationContext.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.0.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

    <context:component-scan base-package="shoe.example"/>
    <aop:aspectj-autoproxy/>
</beans>
Line Description
13 Enable component scanning. This will search the classpath for any classes under the package shoe.example and auto-register (add into the spring factory) classes with any number of annotations. In my case, I'm using: @Component, @Repository, @Service.
14 Turn on Spring AOP. This will take any classes with the annotation @Aspect and apply them to any beans retrieved from the spring container. This includes beans looked up from the application context directly or using the annotations @Autowired or Inject.

Basic Aspect

Now that AOP is enabled, we will create an @Aspect:

InformEntriesAndExists.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package shoe.example;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

import javax.ws.rs.Path;

@Aspect
@Component
public class InformEntriesAndExists {
    @Around(
            value = "@target(service) && @target(path)",
            argNames = "jp,service,path")
    public Object reportRestEndpoints(
            ProceedingJoinPoint jp,
            Service service,
            Path path) throws Throwable {
        return executeShell(jp, "rest endpoint");
    }

    @Around("@target(service)")
    public Object reportServiceEntry(
            ProceedingJoinPoint jp,
            Service service) throws Throwable {
        return executeShell(jp, "service");
    }

    @Around("@target(repository)")
    public Object reportResourceExecution(
            ProceedingJoinPoint jp,
            Repository repository) throws Throwable {
        return executeShell(jp, "repository");
    }

    private Object executeShell(
            ProceedingJoinPoint jp,
            String which) throws Throwable {
        System.out.printf("%s - start: %s\n", which, jp.getSignature());
        try {
            Object result = jp.proceed();
            System.out.printf("%s - finish: %s\n", which, jp.getSignature());
            return result;
        } catch (Throwable t) {
            System.out.printf("%s - failing: %s\n", which, jp.getSignature());
            throw t;
        }
    }
}
Line Description
12 This is an aspect class. It includes information on how to select code to be augmented (pointcuts) along with the code that will be added (advice) to the targeted code.
13 This code will be auto-discovered by Spring
15 Around the execution of matching methods, apply this code.
16 Classes that implement the Service interface and the Path interface will have this code added to all of their public methods. Note that "service" here is a symbolic, named reference to a parameter passed into this method. This also gives the so-called advice code access to the annotation while it executes (assuming the annotation has been configured to be available at runtime).
20 The name "service" is of the fully-qualified java type: org .springframework.stereotype.Service and it is the annotation that will be used in selecting target classes.
25 Classes that implement the service annotation will have the following code added to all of their public methods.
32 Classes that implement the repository interface will have the following code added to all of their public methods.

One of the informed classes

Spring applies the aspect to classes with certain annotations. Here is one such class:

SomeService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package shoe.example;

import org.springframework.stereotype.Service;

import javax.inject.Inject;

@Service
public class SomeService {
    @Inject
    SomeComponent component;

    public void method1() {
        component.method1();
    }

    public void methodThrowingException() {
        throw new RuntimeException();
    }
}

Nothing in this class would suggest that it impacted by the aspect. If you are using spring-aware IDE, then you might notice clues in the editor window that this class is informed by an aspect.

Demonstrate the aspect

Rather than wonder if this works or not, here is a sample test that checks to see if the apsect is correctly being applied at runtime:

ExerciseAspectTest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package shoe.example;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.inject.Inject;
import java.io.PrintStream;

import static org.mockito.Mockito.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class ExerciseAspectTest {
    @Inject
    SomeComponent component;

    @Inject
    SomeRepository repository;

    @Inject
    SomeService service;

    @Inject
    SomeRestEndpoints restEndpoint;

    private PrintStream originalOut;
    private PrintStream streamSpy;
    public static final String ANY_STRING = anyString();

    @Before
    public void redirectOut() {
        originalOut = System.out;
        streamSpy = mock(PrintStream.class);
        System.setOut(streamSpy);
    }

    @After
    public void restoreOut() {
        System.setOut(originalOut);
    }

    @Test
    public void shouldProduceNoOutputForComponent() {
        component.method1();
        verifyZeroInteractions(streamSpy);
    }

    @Test
    public void shouldProductOutputForRepository() {
        repository.save(this);
        verify(streamSpy, times(2)).printf(ANY_STRING, ANY_STRING, ANY_STRING);
    }

    @Test
    public void shouldProduceOutputOnRestEndpoint() {
        restEndpoint.method1();
        verify(streamSpy, times(2)).printf(ANY_STRING, ANY_STRING, ANY_STRING);
    }
}

Conclusions

Spring AOP is a method-only oriented version of Aspect Oriented Programing. It is possible to do more fine-grained AOP with Spring by actually using AspectJ directly. However, for my needs that is not necessary.

If you have used other Aspect Oriented Programming tools, you might be surprised at the simplicity of some of the point-cuts. There are significant differences between general AOP and what Spring offers:

  1. Spring AOP only targets methods
  2. Spring AOP only targets public methods
  3. Spring AOP only targets objects retrieved from the application context
  4. Will never target @Aspect annotated classes (too bad, it’s fun to apply aop at multiple layers, but crazy in production code)

If you are using Spring and using some of its features like declarative transaction demarcation, then you are conceptually, and possibly literally, already using AOP. While “raw” AOP never became mainstream, it is heavily used in may too sets and in several places you might not expect.

Published 01 July 2012

" Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.