Aspect Oriented Programming with Spring

Aspect-Oriented Programming (AOP) complements Object-Oriented Programming (OOP) by providing another way of thinking about program structure. The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the aspect. Aspects enable the modularization of concerns such as transaction management that cut across multiple types and objects. (Such concerns are often termed crosscutting concerns in AOP literature.)

 

One of the key components of Spring is the AOP framework. While the Spring IoC container does not depend on AOP, meaning you do not need to use AOP if you don’t want to, AOP complements Spring IoC to provide a very capable middleware solution.

Implementing a Custom Spring Aspect Oriented Programming Annotation

 

First, let’s add our Maven dependencies.

<dependencies>
    ....
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    ....
</dependencies>

Creating a Custom Annotation

The annotation we are going to create is the one that will be used to log information about controller methods. Let’s create our annotation:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogControllerInfo {
}

Creating the Aspect

Now that we have our annotation, let’s create our aspect. This is just the module that will encapsulate our cross-cutting concern, which is, in our case, the method execution time logging. All it is is a class,

@Aspect
@Component
public class ControllerAspect {
}

Creating Our Pointcut and Advice

Now, let’s create our pointcut and advice. This will be an annotated method that lives in our aspect:

@Before("@annotation(LogControllerInfo)")
public Object LogControllerInfo(ProceedingJoinPoint pjp) throws Throwable {
    return pjp.proceed();
}

Logging controller info

Now that we have our skeleton in place, all we need to do is add some extra logic to our advice. We will add a parameter that can be found in every controller. Every controller that has the HttpServletRequest parameter will be processed by the @LogControllerInfo annotation.

@Around("@annotation(LogControllerInfo) && args(..,request)")
public Object LogControllerInfo(ProceedingJoinPoint jp, HttpServletRequest request) 
throws Throwable {
        System.out.println(jp.getSignature());
        System.out.println(request.getMethod());
        System.out.println(request.getServletPath());
        return jp.proceed();
}

Controller method

According to the rules, the method has to have the LogControllerInfo annotation, and the HttpServletRequest parameter. For example:

@GetMapping("/{id}")
@LogControllerInfo
public ResponseEntity getOne(@PathVariable long id, HttpServletRequest request){
      ...
}

After execution, we should see the following logged to the console:

ResponseEntitycom.lilly021.web.rest.controller.AccountController.getOne(id,HttpServletRequest)
GET
/api/accounts/5