How to work with Spring AOP and AspectJ, Aspect, Advice, Pointcut, JoinPoint, Annotation | Spring Boot

Aspect-Oriented Programming (AOP) helps us cut across many methods, objects and type within the application without embedding it in the business logic. With Spring AOP and AspectJ, you can add additional behaviour to existing code without modifying the code itself.

The tutorial guides you how to work with Spring AOP and AspectJ using Spring Boot.

I. Technology

– Java 1.8
– Maven 3.3.9
– Spring Tool Suite – Version 3.8.1.RELEASE
– Spring Boot: 1.4.0.RELEASE

II. Overview

1. Goal

To build an application that uses Aspect on 4 services:
CustmerAspect on CustomerService: with simple expression
CustmerAspectPointcut on CustomerAnotherService: with @Pointcut Annotation and implement Aspect on its own funtions
CustmerAdvanceAspect on CustomerAdvanceService: with @Around – most powerful kind of advice, and more usages of JoinPoint for getting argument information
CustmerAspectAnnotation on CustomerLoggableService: implement Aspect by creating custom annotation @Loggable

2. Project Structure

springaop-aspectj-project-structure

3. Step to do

– Create Spring Boot project & add Dependencies
– Create a DataModel class
– Create Services
– Create Aspect Classes and Inferface
– Create Spring Bean Configuration File
– Create a Web Controller
– Run Spring Boot Application & Enjoy Result

4. Demo Video

III. Practice

1. Create Spring Boot project & add Dependencies

– Open Spring Tool Suite, on Menu, choose File -> New -> Spring Starter Project, then fill each fields:
springaop-aspectj-startproject

Click Next, in Web: choose Web:
springaop-aspectj-configweb

Click Finish. Spring Boot project will be created successfully.

– Open pom.xml, add dependencies for Spring Context, Spring AOP and AspectJ:

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-context</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjweaver</artifactId>
</dependency>

2. Create a DataModel class

Under package model, create class Customer.
Content of Customer.java:


public class Customer {
	private long id;
	private String firstName;
	private String lastName;
	
	public Customer() {
	}

	public Customer(long id, String firstName, String lastName) {
		this.id = id;
		this.firstName = firstName;
		this.lastName = lastName;
	}

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
	
	@Override
	public String toString() {
		return String.format("Customer[id=%d, firstName='%s', lastName='%s']", id, firstName, lastName);
	}
}

3. Create Services

All Service Classes will be created under package service.

CustomerService.java:


@Component
public class CustomerService {

	private static Map store = new HashMap();
	static {
		store.put(1L, new Customer(1, "Jack", "Smith"));
		store.put(2L, new Customer(2, "Adam", "Johnson"));
	}

	public Customer getCustomerById(long id) {
		return store.get(id);
	}

	public Customer setCustomerFirstName(String firstName, long id) {
		Customer cust = store.get(id);
		cust.setFirstName(firstName);
		return cust;
	}

	public Customer setCustomerLastName(String lastName, long id) {
		Customer cust = store.get(id);
		cust.setLastName(lastName);
		return cust;
	}

	public List findCustomerByLastName(String lastName) {
		List listCust = new ArrayList<>();

		for (Long id : store.keySet()) {
			if (store.get(id).getLastName().equals(lastName)) {
				listCust.add(store.get(id));
			}
		}

		return listCust;
	}

	public List findAllCustomers() {
		List listCust = new ArrayList<>();

		for (Long id : store.keySet()) {
			listCust.add(store.get(id));
		}

		return listCust;
	}
}

Content of CustomerAnotherService.java and CustomerAdvanceService.java are the same as CustomerService.java.

CustomerAnotherService.java:


@Component
public class CustomerAnotherService {

	// the same as CustomerService.java
}

CustomerAdvanceService.java:


@Component
public class CustomerAdvanceService {

	// the same as CustomerService.java
}

Content of CustomerLoggableService.java is similar to CustomerService.java, but it has annotation @Loggable for method getCustomerById:


@Component
public class CustomerLoggableService {

	private static Map store = new HashMap();
	static {
		store.put(1L, new Customer(1, "Jack", "Smith"));
		store.put(2L, new Customer(2, "Adam", "Johnson"));
	}

	@Loggable
	public Customer getCustomerById(long id) {
		return store.get(id);
	}

	public Customer setCustomerFirstName(String firstName, long id) {
		Customer cust = store.get(id);
		cust.setFirstName(firstName);
		return cust;
	}

	public Customer setCustomerLastName(String lastName, long id) {
		Customer cust = store.get(id);
		cust.setLastName(lastName);
		return cust;
	}

	public List findCustomerByLastName(String lastName) {
		List listCust = new ArrayList<>();

		for (Long id : store.keySet()) {
			if (store.get(id).getLastName().equals(lastName)) {
				listCust.add(store.get(id));
			}
		}

		return listCust;
	}

	public List findAllCustomers() {
		List listCust = new ArrayList<>();

		for (Long id : store.keySet()) {
			listCust.add(store.get(id));
		}

		return listCust;
	}
}

4. Create Aspect Classes and Interface

All Aspect Classes will be created under package aspect

CustomerAspect.java:


@Aspect
public class CustomerAspect {
	
	@Before("execution(* com.javasampleapproach.springaop.service.CustomerService.getCustomerById(..))")
	public void getCustomerByIdAdvice() {
		System.out.println("Execute advice on getCustomerByIdAdvice()");
	}

	@Before("execution(* com.javasampleapproach.springaop.service.CustomerService.set*(..))")
	public void getAllSetAdvices(JoinPoint joinPoint) {
		System.out.println("Excute advice on Service set Method: " + joinPoint.getSignature().getName());
	}
}

In class above, we use an AOP Advice Types: Before Advice, there are some types:
Before Advice: runs before the execution of join point methods. Annotation: @Before.
After Advice: runs after the join point method finishes executing. Annotation: @After.
After Returning Advice: runs when the method execution returns normally. Annotation: @AfterReturning.
After Throwing Advice: runs when the method execution exits by throwing an exception. Annotation: @AfterThrowing.
Around Advice: can run both before and after the method executes, and to specify when, how and what the method actually gets to execute. Annotation: @Around.

execution is used for matching method execution join points.

CustomerAspectPointcut.java:


@Aspect
public class CustomerAspectPointcut {

	@Before("findCustomerByLastNamePointCut()")
	public void loggingAdvice() {
		System.out.println("Execute logAdvice on findCustomerByLastName()");
	}

	@Pointcut("execution(* com.javasampleapproach.springaop.service.CustomerAnotherService.findCustomerByLastName(..))")
	public void findCustomerByLastNamePointCut() {
	}

	@Before("allMethodsPointcut()")
	public void allServiceMethodsAdvice(JoinPoint joinPoint) {
		System.out.println("Execute logAdvice for AnotherService Method with joinPoint: " + joinPoint.getSignature().getName());
	}

	@Pointcut("within(com.javasampleapproach.springaop.service.CustomerAnotherService)")
	public void allMethodsPointcut() {
	}
}

If we want to use Pointcut expression at many places (in Aspect Class) or implement Aspect on its own funtions, separate Pointcut and Advice Type, we should create an empty method with annotation @Pointcut, then consider it as expression in advice.

within (a Pointcut Designator) is used to limit execution within certain types.
We also have other Pointcut Designator:
this limits execution of methods where the bean reference (Spring AOP proxy) is an instance of the given type.
target limits execution of methods where the target object (application object being proxied) is an instance of the given type.
args limits the execution of methods where the arguments are instances of the given types.

CustomerAspectJoinPoint.java:


@Aspect
public class CustomerAspectJoinPoint {

	@Before("args(firstName, id)")
	public void logStringArguments(String firstName, long id) {
		System.out.println("Passed Arguments: [" + "firstName = " + firstName + "; id = " + id + "]");
	}

	@Before("execution(* com.javasampleapproach.springaop.service.CustomerAdvanceService.setCustomerFirstName(..))")
	public void logAdvice(JoinPoint joinPoint) {
		System.out.println("Before running Advice on " + joinPoint.toString());
		System.out.println("Display Passed Arguments: " + Arrays.toString(joinPoint.getArgs()));
	}

	@Around("execution(* com.javasampleapproach.springaop.service.CustomerAdvanceService.findAllCustomers())")
	public Object logAround(ProceedingJoinPoint joinPoint){
		System.out.println("This is LogAdvice BEFORE excuting Method");
		
		Object returnObj = null;
		
		try {
			returnObj = joinPoint.proceed();
		} catch (Throwable e) {
			e.printStackTrace();
		}

		System.out.println("This is LogAdvice AFTER excuting Method");
		
		return returnObj;
	}
}

Calling proceed() executes the joinpoint method. We can get return value of the method by catching the return value of method proceed().

CustomerAspectAnnotation.java:


@Aspect
public class CustomerAspectAnnotation {
	@Before("@annotation(Loggable)")
	public void myAnnotationAdvice(){
		System.out.println("Execute Advice before Annotation @Loggable");
	}
}

Another approach for Aspect is creating a custom annotation and annotate the methods. That’s why we annotate method getCustomerById() of CustomerLoggableService with @Loggable.

Loggable.java:


import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;

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

}

5. Create Spring Bean Configuration File

Under src/main/resources, create bean.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

	<!-- Enable AspectJ style of Spring AOP -->
	<aop:aspectj-autoproxy />

	<!-- Bean for Services -->
	<bean name="customerService" class="com.javasampleapproach.springaop.service.CustomerService" />

	<bean name="customerAnotherService" class="com.javasampleapproach.springaop.service.CustomerAnotherService" />

	<bean name="customerAdvanceService" class="com.javasampleapproach.springaop.service.CustomerAdvanceService" />

	<bean name="customerAnnotationService" class="com.javasampleapproach.springaop.service.CustomerLoggableService" />

	<!-- Bean for Aspect -->
	<bean name="customerAspect" class="com.javasampleapproach.springaop.aspect.CustomerAspect" />

	<bean name="customerAspectPointcut" class="com.javasampleapproach.springaop.aspect.CustomerAspectPointcut" />

	<bean name="customerAspectJointPoint" class="com.javasampleapproach.springaop.aspect.CustomerAspectJoinPoint" />

	<bean name="customerAspectAnnotation" class="com.javasampleapproach.springaop.aspect.CustomerAspectAnnotation" />
</beans>

6. Create a Web Controller

Under package controller, create class WebController.


@RestController
public class WebController {
	ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
	CustomerService service = context.getBean(CustomerService.class);
	CustomerAnotherService anotherService = context.getBean(CustomerAnotherService.class);
	CustomerAdvanceService advanceService = context.getBean(CustomerAdvanceService.class);
	CustomerLoggableService loggableService = context.getBean(CustomerLoggableService.class);
        
    @RequestMapping("/aspectnormal")
    public String normalAspect(){
    	String result = "";
    	
    	result += service.getCustomerById(1).getFirstName() + "
";
    	result += service.setCustomerFirstName("Peter", 1) + "
";
    	result += service.setCustomerLastName("Levin", 2);
		
		return result;
    }
    
    @RequestMapping("/aspectpointcut")
    public String pointcutAspect(){
    	String result = "";
    	
    	result += anotherService.findCustomerByLastName("Smith") + "
";
    	result += anotherService.findAllCustomers();
		
		return result;
    }
    
    @RequestMapping("/aspectjoinpoint")
    public String joinpointAspect(){
    	String result = "";
    	
    	result += advanceService.setCustomerFirstName("Peter", 1) + "
";
    	result += advanceService.findAllCustomers();
		
		return result;
    }
    
    @RequestMapping("/aspectloggable")
    public String loggableAspect(){   	
    	return loggableService.getCustomerById(1).getFirstName();
    }
}

7. Run Spring Boot Application & Enjoy Result

– Config maven build:
clean install
– Run project with mode Spring Boot App
– Check results:

Request 1
http://localhost:8080/aspectnormal
springaop-aspectj-req1
System shows:


Execute advice on getCustomerByIdAdvice()
Excute advice on Service set Method: setCustomerFirstName
Excute advice on Service set Method: setCustomerLastName

Request 2
http://localhost:8080/aspectpointcut
springaop-aspectj-req2
System shows:


Execute logAdvice for AnotherService Method with joinPoint: findCustomerByLastName
Execute logAdvice on findCustomerByLastName()
Execute logAdvice for AnotherService Method with joinPoint: findAllCustomers

Request 3
http://localhost:8080/aspectjoinpoint
springaop-aspectj-req3
System shows:


Before running Advice on execution(Customer com.javasampleapproach.springaop.service.CustomerAdvanceService.setCustomerFirstName(String,long))
Display Passed Arguments: [Peter, 1]
Passed Arguments: [firstName = Peter; id = 1]
This is LogAdvice BEFORE excuting Method
This is LogAdvice AFTER excuting Method

Request 4
http://localhost:8080/aspectloggable
springaop-aspectj-req4
System shows:


Execute Advice before Annotation @Loggable

IV. Source Code

SpringAOP-AspectJ



By grokonez | September 5, 2016.

Last updated on April 29, 2021.



Related Posts


Got Something To Say:

Your email address will not be published. Required fields are marked *

*