Spring AOP Example Using Annotation

Spring Framework is developed on two core concepts – Dependency Injection and Aspect-Oriented Programming (AOP). Today we will look into the core concepts of Aspect-Oriented Programming and how we can implement it using Spring Framework.

Note: This post has been updated for Spring Framework 7.0. The original 2014 version targeted Spring 4.1 with AspectJ 1.8 and the aspectjrt + aspectjtools dependencies; it now uses aspectjweaver 1.9.25 on the current Spring 7.0 GA line. Because Spring 7 builds on Jakarta EE, the servlet types in the examples now come from jakarta.servlet.* rather than javax.servlet.*. The AOP concepts and @AspectJ annotations themselves are unchanged.

Aspect-Oriented Programming Overview

Most of the enterprise applications have some common crosscutting concerns that are applicable to different types of Objects and modules. Some of the common crosscutting concerns are logging, transaction management, data validation, etc. In Object-Oriented Programming, modularity of application is achieved by Classes whereas in Aspect-Oriented Programming application modularity is achieved by Aspects and they are configured to cut across different classes.

AOP takes out the direct dependency of crosscutting tasks from classes that we can’t achieve through a normal object-oriented programming model. For example, we can have a separate class for logging but again the functional classes will have to call these methods to achieve logging across the application.

Aspect-Oriented Programming Core Concepts

Before we dive into the implementation of AOP in Spring Framework, we should understand the core concepts of AOP.

  1. Aspect: An aspect is a class that implements enterprise application concerns that cut across multiple classes, such as transaction management. Aspects can be a normal class configured through Spring XML configuration or we can use Spring AspectJ integration to define a class as Aspect using the @Aspect annotation.
  2. Join Point: A join point is a specific point in the application such as method execution, exception handling, changing object variable values, etc. In Spring AOP a join point is always the execution of a method.
  3. Advice: Advices are actions taken for a particular join point. In terms of programming, they are methods that get executed when a certain join point with a matching pointcut is reached in the application. You can think of Advice as Struts2 interceptors or Servlet Filters.
  4. Pointcut: A pointcut is an expression that matches join points to determine whether advice needs to be executed. Spring uses the AspectJ pointcut expression language to match against join points.
  5. Target Object: This is the object on which advice is applied. Spring AOP is implemented using runtime proxies, so this object is always a proxied object: a subclass is created at runtime where the target method is overridden and advice is woven in based on its configuration.
  6. AOP proxy: Spring AOP creates proxy classes that combine the target object with the advice invocations; these are the AOP proxy classes. A JDK dynamic proxy is used when the target implements an interface, and a CGLIB proxy otherwise (CGLIB has been bundled with Spring since 3.2, so no extra dependency is needed).
  7. Weaving: This is the process of linking aspects with other objects to create the advised proxy objects. It can happen at compile time, load time, or runtime; Spring AOP performs weaving at runtime.

AOP Advice Types

Based on the execution strategy of advice, they are of the following types.

  1. Before Advice: This advice runs before the execution of join point methods. Use the @Before annotation to mark a method as Before advice.
  2. After (finally) Advice: This advice runs after the join point method finishes, whether it returns normally or throws an exception. Use the @After annotation to create After advice.
  3. After Returning Advice: Sometimes we want advice to run only if the join point method completes normally. Use the @AfterReturning annotation to mark a method as After Returning advice.
  4. After Throwing Advice: This advice runs only when the join point method throws an exception; it can be used to roll back a transaction declaratively. Use the @AfterThrowing annotation for this type of advice.
  5. Around Advice: This is the most powerful advice. It surrounds the join point method, letting you decide whether to invoke it and run code both before and after its execution. It is the responsibility of Around advice to invoke the join point method and return its value if it returns something. Use the @Around annotation to create Around advice methods.

The points mentioned above may sound confusing but when we will look at the implementation of Spring AOP, things will be more clear. Let’s start creating a simple Spring project with AOP implementations. Spring provides support for using AspectJ annotations to create aspects and we will be using that for simplicity. All the above AOP annotations are defined in org.aspectj.lang.annotation package.

Spring AOP AspectJ Dependencies

<properties>
    <spring.version>7.0.8</spring.version>
    <aspectj.version>1.9.25</aspectj.version>
</properties>
<dependencies>
    ......
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>${aspectj.version}</version>
    </dependency>
    ......
</dependencies>

For proxy-based Spring AOP you only need aspectjweaver on the classpath; the older aspectjrt + aspectjtools pair is meant for compile-time or load-time weaving with the AspectJ compiler, which Spring AOP does not use. The original post pinned Spring 4.1.1 and AspectJ 1.8.2; both are now bumped to the current GA lines (Spring 7.0.8, AspectJ 1.9.25), and the unrelated Hibernate/Druid properties have been dropped since no example here touches them. You can get the full configuration on GitHub.

Spring Bean Configuration with AOP

If you are using Spring Tool Suite, you have the option to create a “Spring Bean Configuration File” and choose the AOP schema namespace; with any other IDE you can simply add it to the Spring bean configuration file.

<!-- Enable AspectJ style of Spring AOP -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<bean id="viewAspect" class="org.verwandlung.voj.aspect.ViewAspect"></bean>
  1. Declare the AOP namespace with xmlns:aop="http://www.springframework.org/schema/aop"
  2. Add aop:aspectj-autoproxy element to enable Spring AspectJ support with auto proxy at runtime
  3. Configure Aspect classes as other Spring beans

You can see that I have a lot of aspects defined in the spring bean configuration file, it’s time to look into that one by one.

Controller Class

@Controller
@RequestMapping(value = "/")
public class DefaultController {
    @RequestMapping(value = "/*", method = RequestMethod.GET)
    public ModelAndView indexView(HttpServletRequest request, HttpSession session) {
        ModelAndView view = new ModelAndView("index");
        return view;
    }
}

Before and After Aspect Example

@Aspect
public class ViewAspect {
    @Before("execution(* org.verwandlung.voj.controller.*.*View(..))")
    public void beforeAspect() {
        System.out.println("This method will be invoked before the aspect.");
    }

    @After("execution(* org.verwandlung.voj.controller.*.*View(..))")
    public void afterAspect() {
        System.out.println("This method will be invoked after the aspect.");
    }
}

Important points in the above aspect class are:

  • Aspect classes are required to have @Aspect annotation.
  • @Before annotation is used to create Before advice
  • @After annotation is used to create After advice
  • beforeAspect() advice will execute for any Spring Bean method with signature public ModelAndView indexView(any parameters). This is a very important point to remember, if we will create Controller bean the advice will not be applied. Only when we will use ApplicationContext to get the bean, advice will be applied.

We will look for advice in action in a test class after we have looked into all the different types of advice.

Around Aspect Example

As explained earlier, Around advice surrounds the join point method, so we can run code both before and after it, and even decide whether to invoke the target method at all or alter its return value. This is the most powerful advice type and should be used sparingly. Rather than redefine the whole class, add this method to the ViewAspect we started above:

@Around("execution(* org.verwandlung.voj.controller.*.*View(..))")
public ModelAndView aroundView(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    System.out.println("Before invoking the *View() method");
    ModelAndView view = (ModelAndView) proceedingJoinPoint.proceed();
    System.out.println("After invoking the *View() method");
    return view;
}

Around advice must take a ProceedingJoinPoint as its first argument and call proceed() to invoke the advised method. If that method returns a value, it is the advice’s responsibility to return it to the caller (for void methods the advice may return null). Because the advice wraps the target, it controls the method’s input, output, and whether it executes at all.

Passing Parameters to AOP Advice

In Spring AOP you can also bind arguments of the intercepted method into the advice. The args() part of the pointcut below captures the HttpSession argument and passes it straight into the advice method, which is handy when the advice needs the caller’s data. Again, add this method to ViewAspect:

@Around("execution(* org.verwandlung.voj.controller.*.*View(..)) && args(.., session)")
public ModelAndView injectProfile(ProceedingJoinPoint proceedingJoinPoint, HttpSession session) throws Throwable {
    ModelAndView view = (ModelAndView) proceedingJoinPoint.proceed();
    User user = (User) session.getAttribute("user");
    view.addObject("profile", user);
    return view;
}

This advice intercepts every *View(..) method in the controller package that takes an HttpSession as its last argument (that is what args(.., session) requires), reads the logged-in User from the session, and exposes it to the view as profile.

Testing the Aspects in Action

Now we can deliver on the promise made earlier: the advice only fires when the bean is obtained from the Spring container, because that is when you get the AOP proxy rather than the raw object. The test below loads the context, pulls DefaultController out of it, and calls indexView() through the proxy:

import org.junit.jupiter.api.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpSession;

public class ViewAspectTest {
    @Test
    public void testViewAspect() {
        try (var context = new ClassPathXmlApplicationContext("applicationContext.xml")) {
            DefaultController controller = context.getBean(DefaultController.class);
            controller.indexView(new MockHttpServletRequest(), new MockHttpSession());
        }
    }
}

With only the Before and After advice in place, running the test prints:

This method will be invoked before the aspect.
This method will be invoked after the aspect.

Add the Around advice as well and, because @Around has the highest precedence within an aspect, its messages wrap the others:

Before invoking the *View() method
This method will be invoked before the aspect.
This method will be invoked after the aspect.
After invoking the *View() method

If you had instead written new DefaultController().indexView(...), none of these messages would appear: there is no proxy, so there is nothing to weave the advice into. That is the practical meaning of “advice applies only to beans fetched from the container.”

Conclusion

We covered the core AOP vocabulary (aspect, join point, pointcut, advice, weaving), the five advice types, and how to wire them up with @AspectJ annotations and a single <aop:aspectj-autoproxy/> switch. The complete, runnable Spring project these snippets come from lives in the voj repository on GitHub.

Reference