Spring Boot Implement AOP with Spring Boot Starter AOP
Tags: Java Spring Boot Spring AOP AspectJ 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.
On the New Spring Starter Project Dependencies popup choose Thymeleaf and Spring Web dependency as below screenshot.
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
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.
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:
Happy Coding 😊