Aspect Oriented Programming with Spring Boot

Aspects? Pointcuts? Join point? Huh?

Posted on 08 June 2017

While much of the terminology sounds rather complex Spring Boot has an excellent AOP starter that makes actually using AOP a breeze. In this blog post I’m going to (hopefully) make you as enthusiastic about Spring AOP as I am. As usual this blog post comes with a repository with runnable code.

Introduction

So, let’s first take a step back and think about what problem AOP tries to solve. Most applications have something called cross cutting concerns. So what is a cross cutting concern? Cross cutting concerns are 'things' you want to do in multiple places in your application. A few examples:

  • Logging REST calls

  • Gathering metrics on method calls

  • Only let 'admin' users access functionality

  • Only let requests from localhost access functionality

A naive approach of handling these concerns would be to just implement them in every single place. So you could for example add a few lines of code to grab the authentication header from a HTTP request object, check if the user is indeed an admin and throw an exception if this is not the case. But this would add multiple lines of 'the same' code to every method.

You also might be tempted to solve this with an OO approach with a liberal application of inheritance. This is a common pitfall. A Controller 'is' not a Repository and 'is' not a Service. Having them all inherit from some kind of base component class will cause problems down the line where for example a Repository and Service do share concerns that a Controller does not share: you can only inherit from one base class. So what we really need is a form of behavior composition instead of behavior inheritance.

Of course most of us would probably use one of the many Spring frameworks to solve this for us. But let’s for now pretend Spring Security does not exist ;)

Aspect Oriented Programming

In the project I’m working on we use Spring AOP for monitoring external service calls and repository calls. The durations of the calls as well as the success / fail state get fed to Prometheus. Failures also affect the health state reported through Spring Actuator which is used in our dashboard and by Kubernetes to check if our services are working properly.

For this blog post I will use a small Todo REST service as a basis to implement our aspects. You can find the source here. This service contains three examples of how to apply AOP.

When starting out with AOP the terminology can be a bit of a hurdle. The Spring documentation does a pretty good attempt at explaining these. The most important ones are Aspect, Join Point, Advice and Pointcut. The terminology is somewhat confusing because when you use AspectJ a Join Point can be almost any piece of code. In the case of Spring AOP however a Join Point is always a method.

An Aspect is a piece of functionality (such as functionality to log method call durations). It will be defined in a class annotated with the @Aspect annotation. It’s a module that contains one more more advices; pieces of code that get applied to join points (in the case of Spring AOP join points are always methods). Join points (methods) are selected in Pointcut expressions: declarative patterns that tell Spring AOP which methods to apply your logic to.

A very simply Pointcut expressions is execution(public * *(..)): this expression applies to any public method, no matter the return type, class name or method name that has zero or more arguments. Another example is execution(public void set*(..)): this expression selects any method that is public, has the void return type, has a name that begins with set and has zero or more arguments. So the execution Pointcut is used when you want to select methods based on a pattern.

Another very useful Pointcut is the @annotation pointcut. For example @annotation(org.springframework.web.bind.annotation.RequestMapping): this will select anything that has the Spring @RequestMapping annotation. So this selects all methods handling REST calls.

You can also combine expressions like these into a single Pointcut. For example: @annotation(org.springframework.web.bind.annotation.RequestMapping) && execution(public * admin*(..)) This expression selects any REST endpoint who’s method name starts with admin. This way you can create your own security framework that enforces admin access requirements on admin REST calls. Pretty neat!

The syntax is incredibly powerful and flexible, you can find more examples in the AspectJ documentations. Keep in mind though that Spring only supports a subset of pointcuts, it does not support call, get, set, preinitialization, staticinitialization, initialization, handler, adviceexecution, withincode, cflow, cflowbelow, if, @this, and @withincode. Using these will result in an IllegalArgumentException being thrown.

Spring AOP versus AspectJ

Spring AOP offers an simpler but less powerful implementation of AOP than for example AspectJ. AspectJ works by taking your source code, running it through a separate compiler (AJC) and weaving together your aspects and Java class files. This can be done in the build or at runtime using load time weaving. The benefit of using AspectJ is that it can insert functionality almost everywhere: it can inject code in the middle of a method and replace calls to System.out with logging calls for example.

Spring AOP is simpler. It can only 'wrap' methods in Spring Beans (so non-bean classes are skipped) and it does so by inspecting them on load and generating a proxy for them. While you are limited to wrapping methods and as such is less powerful than AspectJ it just works out of the box in any Spring project without having to set up AspectJ in the build and/or set up load time weaving.

I personally really like that Spring uses the exact same semantics as AspectJ and even borrows it’s annotations. That way moving between the frameworks should be fairly easy.

Spring AOP

Getting started

First we need to add Spring-AOP to our POM:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>${spring.boot.version}</version>
</dependency>

Then we also need to enable AOP proxy generation in the configuration:

@Configuration
@EnableAspectJAutoProxy
public class ApplicationConfiguration {
}

That’s all we need to set up!

Duration logging Aspect

So let’s start with a simple Aspect: we want to log the duration of method calls. So we get the start time, do the call, get the end time, calculate the delta and log the result. So assuming we have a TodoRepository class with a get() method:

public List<TodoList> get(final UUID userId) {
    return db.computeIfAbsent(userId, u -> new ArrayList<>());
}

If we want to log the duration of the database access we would, without AOP, do something like this:

public List<TodoList> get(final UUID userId) {
  long start = System.currrentTimeMillis();
  List<TodoList> list = db.computeIfAbsent(userId, u -> new ArrayList<>());
  long end = System.currrentTimeMillis();

  log.info("UserService.login took {} ms", end - start);

  return list;
}

Nasty code indeed! I would personally not want to have to add that to every method. Fortunately with Spring AOP we don’t have to! There are different ways to have AOP figure out where to add aspects. In this example I am going to create a simple marker annotation; @Timed:

public @interface Timed {
}

It doesn’t contain any values; it just acts as a marker we can add to methods. For example our get method:

@Timed
public List<TodoList> get(final UUID userId) {
    return db.computeIfAbsent(userId, u -> new ArrayList<>());
}

Now that we have something to 'hook' into we can create our own aspect:

@Aspect
@Component
@Slf4j
public class TimeLogAspect {
    @Around("@annotation(com.nibado.example.springaop.aspects.Timed) && execution(public * *(..))")
    public Object time(final ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long start = System.currentTimeMillis();

        Object value;

        try {
            value = proceedingJoinPoint.proceed();
        } catch (Throwable throwable) {
            throw throwable;
        } finally {
            long duration = System.currentTimeMillis() - start;

            log.info(
                    "{}.{} took {} ms",
                    proceedingJoinPoint.getSignature().getDeclaringType().getSimpleName(),
                    proceedingJoinPoint.getSignature().getName(),
                    duration);
        }

        return value;
    }
}

So, what’s happening here? I created a TimeLogAspect that contains the logic to be able to log the duration of methods marked with @Timed. The Aspect has a single method time(). This is the Advice. It’s basically the logic surrounding the method marked with @Timed. The method that this sits 'around' is our Join Point. It’s not simply called a method because in the case of AspectJ for example there are multiple options. In Spring AOP a join point is always a method execution. We can access the join point and the method (including it’s class, name, annotations, etc.) through the ProceedingJoinPoint instance Spring supplies us with.

The @Around annotation instructs Spring AOP to create a proxy around the method call. An @Around advice allows us to do something both before and after execution. There are multiple types of Advice:

  • Before: executes before the join point is executed

  • After returning: executes only after a join point executes normally (doesn’t throw anything)

  • After throwing: executes only when a join point throws an exception

  • After: executes always after a join point, even if it throws an exception

  • Around: the most powerful type; lets you execute code before and after join point execution

The value of the @Around annotation is the Pointcut. This is an expression that uses patterns to match methods:

@annotation(com.nibado.example.springaop.aspects.Timed) && execution(public * *(..))

This instructs Spring AOP that we want to match methods that have the Timed annotation and have public access. We don’t care about the return type, class or method name or what arguments it has.

So, I have annotated all the methods in TodoRepository with the @Timed annotation. We can now start the application and send HTTP requests and we should then see log entries appear:

Note
This Todo service uses a user-id UUID header to identify the user in lieu of authentication
$ curl -H "user-id: 00000000-0000-0000-0000-000000000000" http://127.0.0.1:8080/todo/me
{"todoLists":[]}

2017-06-09 11:01:05 INFO  c.n.e.s.aspects.TimeLogAspect - TodoRepository.get took 6 ms

So what happens under the hood? At startup Spring AOP creates a proxy class for every bean it needs to add Advice to. So if you’d set a breakpoint somewhere in the TodoController you’d see that the injected TodoRepository class is in fact a proxy: com.nibado.example.springaop.todo.TodoRepositoryEnhancerBySpringCGLIB67548eb4.

This proxy class wraps all the methods with the advice we created.

While setting this up takes a bit of work it scales incredibly well. You now only need to add a single annotation to a method instead of multiple lines of code. In our project we have a common library shared between micro services so we only need to implement this once and then reuse it anywhere.

If you just want to annotate all public methods in all your repositories without using a marker annotation you can just select all your repositories in your Pointcut if you want. Just change the Pointcut expression inside the @Around advice to bean(*Repository). This way you don’t need the @Timed marker annotations. I personally like the use of these annotations because they make it clear something is happening here and gives me the control to select which methods to time.

Note
The bean Pointcut is Spring AOP specific and does not exist in AspectJ, but you can do the same with a within() Pointcut

REST call logging Aspect

So let’s create another aspect. I would like to log every REST call without having to add anything to our controller. Not even a marker annotation. Is that possible? Sure! There’s different approached we could take. I could for example have a Pointcut on any public method in any class with a name ending on Controller. In this case however I chose to have the Pointcut on public methods that have the @RequestMapping annotation.

Since I want to log the path, method and remote IP address I also need to access the HTTP request inside the Aspect. Is this possible? Sure!

The Aspect looks like this:

@Aspect
@Component
@Slf4j
public class RequestLogAspect {
    @Around("@annotation(org.springframework.web.bind.annotation.RequestMapping) && execution(public * *(..))")
    public Object log(final ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
                .currentRequestAttributes())
                .getRequest();

        Object value;

        try {
            value = proceedingJoinPoint.proceed();
        } catch (Throwable throwable) {
            throw throwable;
        } finally {
            log.info(
                    "{} {} from {}",
                    request.getMethod(),
                    request.getRequestURI(),
                    request.getRemoteAddr(),
                    request.getHeader("user-id"));
        }

        return value;
    }
}

In the advice (again an Around advice) we get the current HTTP request from the Spring RequestContextHolder. It executes the underlying method and then logs the information available in the request. If we perform a GET we should see a log entry:

curl -H "user-id: 00000000-0000-0000-0000-000000000000" http://127.0.0.1:8080/todo/me

2017-06-08 18:54:11 INFO  c.n.e.s.aspects.RequestLogAspect - GET /todo/me from 127.0.0.1

Pretty neat!

Access restriction Aspect

So now that we know how to apply AOP to our methods we can build some fun stuff. So let’s build our own Security framework! I want to be able to restrict access to REST calls based on whether the call is local and whether the uses has an admin role or not. In lieu of authentication we are just going send a bearer token with the value "ADMIN". But first I am going to create my own custom annotation:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Restrict {
    boolean localOnly() default false;
    boolean admin() default true;
}

It contains two boolean values that allow me to only let local requests, admin request or requests that are both local and have admin credentials through. I have also set the retention policy to runtime so that I can use it’s values inside the advice (without it you won’t be able to get it from the JoinPoint!). I am going to add it to the delete all endpoint:

@RequestMapping(value = "", method = RequestMethod.DELETE)
@Restrict
public Callable<ResponseEntity<?>> deleteAllTodos() {
    log.info("DELETE all todo's");
    return () -> {
        repository.deleteAll();

        return ResponseEntity.accepted().build();
    };
}

It uses the default of the user needing admin credentials.

Now I can create an aspect that contains the logic:

@Aspect
@Component
@Slf4j
public class RestrictAspect {
    @Before("@annotation(com.nibado.example.springaop.aspects.Restrict) && execution(public * *(..))")
    public void restrict(final JoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Restrict annotation = signature.getMethod().getAnnotation(Restrict.class);

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
                .currentRequestAttributes())
                .getRequest();

        if (annotation.admin() && !isAdmin(request)) {
            throw new ForbiddenException("Need admin access");
        }

        if (annotation.localOnly()
                && !request.getRemoteAddr().equals("127.0.0.1")
                && !request.getRemoteAddr().equals("0:0:0:0:0:0:0:1")) {
            throw new ForbiddenException("Only possible from localhost");
        }
    }

    private static boolean isAdmin(final HttpServletRequest request) {
        String authorization = request.getHeader("Authorization");

        return authorization != null
                && authorization.replace("Bearer ", "").equalsIgnoreCase("admin");
    }
}

In this example because I don’t need to 'surround' the method being executed I’m just going to use a Before advice. It’s a best practice to use the simplest type possible.

Inside the restrict method I grab the Restrict annotation from the method. This allows me to check it’s values and use it to decide whether I should check if the user has Admin access and if it’s a local connection. So let’s see if this works:

$ curl -I -X DELETE http://127.0.0.1:8080/todo
HTTP/1.1 403
{"code":"FORBIDDEN_ACTION","message":"Need admin access"}

$ curl -I -X DELETE -H "Authorization:Bearer ADMIN" http://127.0.0.1:8080/todo
HTTP/1.1 202

Success! So here we have created our very own authorization framework!

Conclusion

One of the most awesome aspects (heh) of Spring is how extensible it is. You can use servlet filters, interceptors, controller advice (for microservice versioning for example) to plug in your own pieces of logic almost anywhere. Of these options AOP is probably the most versatile and powerful one. It can be applied to any method in any bean using whatever pattern you prefer. Using AOP makes implementing cross cutting concerns a lot less cumbersome.

There is one pitfall though. If you like me work on a microservice architecture it is important to not create too much 'magic'. If you use Pointcuts that for example apply to every public method in a controller it can make it hard for developers to understanding what code is interfering with their newly created controller. Make sure that everyone is aware that AOP is being used and that they have a basic understanding of what is happening.