AOP is one of those things in the Spring Framework that we might not need, even for basic implementation, which we have an example for in this post. But we should always consider using Spring AOP whenever possible to have a modular and maintainable application. The benefit we could reap is enormous. Of course, it depends on the type of project we work on. Sadly, not many people (even experienced ones) appreciate the benefits of separating cross-cutting concerns. They would instead code away without maintainability in mind. If they implemented some modularity, it would be in some form of experimental feature (for sport).
This post shows how to use basic Spring Framework AOP (still without Spring Boot).
Minimum Dependency For Spring AOP
To get us started with our AOP example, we need a Spring project that works for AOP – particularly the dependencies it needs. At a minimum, we would require the following dependencies in our pom.xml file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | ... <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.25</version> </dependency> <!-- START: AOP --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.3.25</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.25</version> </dependency> <!-- END: AOP --> ... |
We do not have a lot of dependencies for our basic Spring AOP example, do we? Nope! We only need the spring-context, and Maven will take the rest of the transitive core dependencies. Thus, we only have two AOP-related dependencies, which are spring-aop and spring-aspects. These dependencies have other dependencies which Maven will take care of. These dependencies are enough to get us going.
Service Bean To Use AOP On
We need a test subject for our basic Spring AOP example, and we can use a service bean! Typically, we annotate this type of bean with @Service. Thus, a service bean. Furthermore, it has six methods (our target join points), but we will only test five using our basic Spring AOP example.
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 | import org.springframework.stereotype.Service; @Service public class PersonService { public void createPersonForAOPBefore() { System.out.println("--- createPersonForAOPBefore"); } public void createPersonForAOPAfterReturning() { System.out.println("--- createPersonForAOPAfterReturning"); } public void createPersonForAOPAfterThrowing() { throw new RuntimeException("Test Exception from createPersonForAOPAfterThrowing"); } public void createPersonForAOPAfterFinallyA() { System.out.println("--- createPersonForAOPAfterFinallyA"); } public void createPersonForAOPAfterFinallyB() { System.out.println("--- createPersonForAOPAfterFinallyB"); throw new RuntimeException("Test Exception from createPersonForAOPAfterFinallyB"); } // We will cover this in another post! public void createPersonForAOPAround() { System.out.println("--- createPersonForAOPAround"); } } |
The method createPersonForAOAround is for another post.
The Aspect For Our AOP Example
Before we proceed, here are some terms we need to familiarize with – Aspect, Pointcut, and Advice. First, an Aspect is a class we annotate with @Aspect and @Component (or its derivatives). We use its codes to “extend” our service bean (PersonService) methods. Second, we have Pointcuts. They specify which join points in PersonService we map various “extending” codes to. We typically use the annotations @Before, @After, @AfterThrowing, @AfterReturning, and @Around to specify the pointcuts in the form of expressions. Finally, we have Advice which is the action our codes (plus the semantics of those AOP annotations) perform at those join points.
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 | package com.turreta.learnspring.util; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @Aspect @Component public class MyAspectLogger { @PostConstruct public void postConstruct() { System.out.println("AOP MyAspectLogger is up!"); } @PreDestroy public void preDestroy() { System.out.println("AOP MyAspectLogger is shutting down!"); } @Before("execution(public void com.turreta.learnspring.service.*.createPersonForAOPBefore(..))") public void beforeCreatePerson(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); System.out.println("[@Before] methodName:" + methodName); } @AfterReturning("execution(public void com.turreta.learnspring.service.*.createPersonForAOPAfterReturning(..))") public void afterReturningCreatePerson(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); System.out.println("[@AfterReturning] methodName:" + methodName); } @AfterThrowing("execution(public void com.turreta.learnspring.service.*.createPersonForAOPAfterThrowing(..))") public void afterThrowingCreatePerson(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); System.out.println("[@AfterThrowing] methodName:" + methodName); } @After("execution(public void com.turreta.learnspring.service.*.createPersonForAOPAfterFinallyA(..))") public void afterFinallyACreatePerson(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); System.out.println("[@After - A] methodName:" + methodName); } @After("execution(public void com.turreta.learnspring.service.*.createPersonForAOPAfterFinallyB(..))") public void afterFinallyBCreatePerson(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); System.out.println("[@After - B] methodName:" + methodName); } } |
Then, we test our basic Spring AOP example by invoking those service methods from the main method.
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 | ... public class BareMinimumAopApp { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); AbstractApplicationContext abstractApplicationContext = (AbstractApplicationContext) context; abstractApplicationContext.registerShutdownHook(); PersonService personService = context.getBean(PersonService.class); personService.createPersonForAOPBefore(); personService.createPersonForAOPAfterReturning(); try { personService.createPersonForAOPAfterThrowing(); } catch (Throwable e ) { // Ignore exception System.out.println("--- ### " + e.getMessage()); } personService.createPersonForAOPAfterFinallyA(); try { personService.createPersonForAOPAfterFinallyB(); } catch (Throwable e ) { // Ignore exception System.out.println("--- ### " + e.getMessage()); } System.out.println("\n\nPowered by Turreta"); } } |
When we run the codes, we get the following output.
For more information about the codes, please get them from our Turreta GitHub repository.