Spring Boot Implement AOP with Spring Boot Starter AOP

Introduction

In this article, we learn how to implement AOP (Aspect-Oriented Programming) in a Spring Boot Web application. Via step by step to creating the sample Spring Boot web application, we are going to explore how to use Spring Boot Starter AOP library which is the combination of Spring AOP and AspectJ libraries to easily implement AOP in Spring Boot application.

Create New Spring Boot Web project

From Spring Tool Suite IDE select menu File > New > Spring Starter Project.

On the New Spring Starter Project popup input new project spring-boot-aop information as following screenshot.

Create Spring Boot Web Project AOP

On the New Spring Starter Project Dependencies popup choose Thymeleaf and Spring Web dependency as below screenshot.

Create Spring Boot Web Project AOP

You can also creating new Spring Boot project using Spring initializr online tool at start.spring.io

Add Spring Boot Starter AOP

Add the following Spring Boot Starter AOP dependency to your build.gradle file.

compile group: 'org.springframework.boot', name: 'spring-boot-starter-aop', version: '2.3.4.RELEASE'

Or use the following XML to add dependency in pom.xml file if you are using Maven build for your project.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>2.3.4.RELEASE</version>
</dependency>

Implement Basic Spring Boot Web

Create a new package named dev.simplesolution.domain and implement Customer class as below.

package dev.simplesolution.domain;

public class Customer {
	private Long id;
	private String firstName;
	private String lastName;
	private String phone;
	
	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;
	}
	public String getPhone() {
		return phone;
	}
	public void setPhone(String phone) {
		this.phone = phone;
	}
	@Override
	public String toString() {
		return "Customer [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + ", phone=" + phone + "]";
	}
}

Create a new package named dev.simplesolution.service and define CustomerService interface.

package dev.simplesolution.service;

import dev.simplesolution.domain.Customer;

public interface CustomerService {
	
	Customer get(Long id);

}

Create a new package named dev.simplesolution.service.impl then implement CustomerServiceImpl class.

Notice that in the get() method we throw exception when the id less thant or equal zero.

package dev.simplesolution.service.impl;

import org.springframework.stereotype.Service;

import dev.simplesolution.domain.Customer;
import dev.simplesolution.service.CustomerService;

@Service
public class CustomerServiceImpl implements CustomerService {

	@Override
	public Customer get(Long id) {
		if(id <= 0) {
			throw new IllegalArgumentException("Customer ID is invalid.");
		}
		Customer customer = new Customer();
		customer.setId(id);
		customer.setFirstName("John");
		customer.setLastName("Doe");
		customer.setPhone("123-456-789");
		return customer;
	}
	
}

Create a new package named dev.simplesolution.controller and add a new controller class CustomerController.

package dev.simplesolution.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import dev.simplesolution.domain.Customer;
import dev.simplesolution.service.CustomerService;

@Controller
public class CustomerController {
	@Autowired
	private CustomerService customerService;
	
	@RequestMapping("/customer")
	public String view(Model model, @RequestParam(value = "customerId") long customerId) {
		Customer customer = customerService.get(customerId);
		model.addAttribute("customer", customer);
		return "viewCustomer";
	}

}

Add view viewCustomer.html to show the customer object property at \src\main\resources\templates\viewCustomer.html, in this view we use Bootstrap CDN CSS.

<!DOCTYPE html>
<html>
	<head>
		<title>View Customer</title>
		<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
	</head>
	<body>
	<div class="container">
		<div class="form-group row">
	    	<label class="col-sm-2 col-form-label">Customer ID</label>
	    	<div class="col-sm-10">
	    		<span class="form-control-plaintext"  th:text="${customer.id}"></span>	
	    	</div>
		</div>
		<div class="form-group row">
	    	<label class="col-sm-2 col-form-label">First Name</label>
	    	<div class="col-sm-10">
	    		<span class="form-control-plaintext"  th:text="${customer.firstName}"></span>	
	    	</div>
		</div>
		<div class="form-group row">
	    	<label class="col-sm-2 col-form-label">Last Name</label>
	    	<div class="col-sm-10">
	    		<span class="form-control-plaintext"  th:text="${customer.lastName}"></span>	
	    	</div>
		</div>
		<div class="form-group row">
	    	<label class="col-sm-2 col-form-label">Phone</label>
	    	<div class="col-sm-10">
	    		<span class="form-control-plaintext"  th:text="${customer.phone}"></span>	
	    	</div>
		</div>
	</div>
	</body>
</html>

Execute the application and visit the link localhost:8080/customer?customerId=100 on your browser to see the output as following screenshot

Spring Boot Web Project AOP Demo

Implement AOP

In previous steps we have finished a simple web page with Spring Boot which includes the controller and service class to handle business logic.

In the following steps we are going to apply AOP to write logging messages for our code without modify the current source code.

Firstly, create a new package named dev.simplesolution.aop and add new class named LogAspect.

package dev.simplesolution.aop;

import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Aspect
@Service
public class LogAspect {
	protected Logger logger = LoggerFactory.getLogger(LogAspect.class);
	
}

Implement before advice to write log message to every method invoke of every class within package dev.simplesolution

@Before("within(dev.simplesolution..*)")
public void logBefore(JoinPoint joinPoint) {
	// Log method name
	logger.info("Invoke method: " + joinPoint.getSignature().toLongString());
	// Log arguments
	if(joinPoint.getArgs() != null) {
		for(Object obj : joinPoint.getArgs()) {
			logger.info("Argument: " + obj.toString());
		}
	}
}

Implement after return advice to write log message when a method return value of all classes within package dev.simplesolution

@AfterReturning(pointcut =  "within(dev.simplesolution..*)", returning = "returnObject")
public void logAfterReturning(JoinPoint joinPoint, Object returnObject) {
	// Log method name
	logger.info("Return from method: " + joinPoint.getSignature().toLongString());
	// Log the return value
	logger.info("Return Object: " + returnObject);
}

Implement after throwing advice to write a log message whenever a method of class within package dev.simplesolution throws an exception.

@AfterThrowing(pointcut = "within(dev.simplesolution..*)", throwing = "ex")
public void logAfterThrowing(Exception ex) {
	// Log the exception message
	logger.info("Error: " + ex.getMessage());
}

The final LogAspect class is as follows.

package dev.simplesolution.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Aspect
@Service
public class LogAspect {
	protected Logger logger = LoggerFactory.getLogger(LogAspect.class);
	
	@Before("within(dev.simplesolution..*)")
	public void logBefore(JoinPoint joinPoint) {
		// Log method name
		logger.info("Invoke method: " + joinPoint.getSignature().toLongString());
		// Log arguments
		if(joinPoint.getArgs() != null) {
			for(Object obj : joinPoint.getArgs()) {
				logger.info("Argument: " + obj.toString());
			}
		}
	}
	
	@AfterReturning(pointcut =  "within(dev.simplesolution..*)", returning = "returnObject")
	public void logAfterReturning(JoinPoint joinPoint, Object returnObject) {
		// Log method name
		logger.info("Return from method: " + joinPoint.getSignature().toLongString());
		// Log the return value
		logger.info("Return Object: " + returnObject);
	}
	
	@AfterThrowing(pointcut = "within(dev.simplesolution..*)", throwing = "ex")
	public void logAfterThrowing(Exception ex) {
		// Log the exception message
		logger.info("Error: " + ex.getMessage());
	}
}

Final application

Your final Spring Boot web application will look like the following structure.

Spring Boot Web Project AOP Source Code Structure

Run the application and visit localhost:8080/customer?customerId=100 then observe the log you can see the application write following as we implemented in LogAspect.

2020-10-17 20:31:32.266  INFO 7896 --- [nio-8080-exec-1] dev.simplesolution.aop.LogAspect         : Invoke method: public java.lang.String dev.simplesolution.controller.CustomerController.view(org.springframework.ui.Model,long)
2020-10-17 20:31:32.266  INFO 7896 --- [nio-8080-exec-1] dev.simplesolution.aop.LogAspect         : Argument: {}
2020-10-17 20:31:32.266  INFO 7896 --- [nio-8080-exec-1] dev.simplesolution.aop.LogAspect         : Argument: 100
2020-10-17 20:31:32.272  INFO 7896 --- [nio-8080-exec-1] dev.simplesolution.aop.LogAspect         : Invoke method: public dev.simplesolution.domain.Customer dev.simplesolution.service.impl.CustomerServiceImpl.get(java.lang.Long)
2020-10-17 20:31:32.272  INFO 7896 --- [nio-8080-exec-1] dev.simplesolution.aop.LogAspect         : Argument: 100
2020-10-17 20:31:32.275  INFO 7896 --- [nio-8080-exec-1] dev.simplesolution.aop.LogAspect         : Return from method: public dev.simplesolution.domain.Customer dev.simplesolution.service.impl.CustomerServiceImpl.get(java.lang.Long)
2020-10-17 20:31:32.275  INFO 7896 --- [nio-8080-exec-1] dev.simplesolution.aop.LogAspect         : Return Object: Customer [id=100, firstName=John, lastName=Doe, phone=123-456-789]
2020-10-17 20:31:32.275  INFO 7896 --- [nio-8080-exec-1] dev.simplesolution.aop.LogAspect         : Return from method: public java.lang.String dev.simplesolution.controller.CustomerController.view(org.springframework.ui.Model,long)
2020-10-17 20:31:32.275  INFO 7896 --- [nio-8080-exec-1] dev.simplesolution.aop.LogAspect         : Return Object: viewCustomer

Try to visit a link that raise exception localhost:8080/customer?customerId=0 then you can see the log as we have implemented in after throwing advice.

2020-10-17 20:32:08.632  INFO 7896 --- [nio-8080-exec-3] dev.simplesolution.aop.LogAspect         : Error: Customer ID is invalid.
2020-10-17 20:32:08.632  INFO 7896 --- [nio-8080-exec-3] dev.simplesolution.aop.LogAspect         : Error: Customer ID is invalid.

Conclusion

In this tutorial we have learned how to use Spring Boot Starter AOP to implement additional processing to the current code without modify the code itself with support of Spring AOP and AspectJ.

Download Source Code

The source code in this article can be found at: github.com/simplesolutiondev/spring-boot-aop

or clone at:

git clone https://github.com/simplesolutiondev/spring-boot-aop.git

or download at:

Download Source Code

Happy Coding 😊