Để tiếp tục của bài viết Aspect Oriented Programming (AOP) và Spring AOP. Trong bài viết này sẽ trình bày chi tiết hơn về Spring AOP. Cách dẫn tạo aspect bằng một vị dụ về việc ghi log cho ứng dụng.
Giới thiệu
Trong bài viết này, mình phát triển một ứng dụng CRUD đơn giản, và sử dụng AOP cho việc ghi log. Cấu trúc dự án như hình bên dưới. Việc ghi log của ứng dụng sẽ triển khai trong lớp LoggingAspect.java
loggingwithaop
│ LoggingWithAopApplication.java
│
├───aspect
│ LoggingAspect.java
│
├───controller
│ ProductController.java
│
├───exception
│ ErrorDetails.java
│ GlobalExceptionHandler.java
│ ResourceNotFoundException.java
│
├───model
│ Product.java
│
├───payload
│ ResponseDTO.java
│
├───repository
│ ProductRepository.java
│
└───service
ProductService.java
Dependency
Thêm dependency sau để sử dụng Spring AOP
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
Tạo logging aspect
Đây là lớp định nghĩa các advice, pointcut liên quan đến việc ghi log cho toàn ứng dụng.
package com.pad.loggingwithaop.aspect;
@Aspect
@Component
public class LoggingAspect {
// khai báo các advice, poincut ở đây
}
Tạo một advice
Ví dụ cho trường hợp: ghi log trước khi phương thức updateProduct()
được thực hiện, cùng xem đoạn code dưới đây và phân tích advice này
@Before("execution(* com.pad.loggingwithaop.service.ProductService.updateProduct(..))")
public void logParamBeforeExecuteUpdateProduct(JoinPoint joinPoint){
log.debug("Log before executing method...");
log.debug("This is qualified name: {}", joinPoint.getSignature().getDeclaringTypeName());
log.debug("This is method name: {}", joinPoint.getSignature().getName());
log.debug("Arguments of method: {}", Arrays.toString(joinPoint.getArgs()));
}
Và kết quả của advice kia như sau:
Phân tích qua advice ví dụ trên
Tạo pointcut
Để hiểu rõ hơn cách viết pointcut, hãy phân tích một vài biểu thức pointcut sau:
Trong trường hợp bạn muốn hạn chế các điểm joint point khớp với biểu thức, bạn có thể sử dụng biểu thức && (và),
|| (hoặc
), ! (phủ định)
để kết xây dựng biểu thức. Biểu thức ở dưới đây có nghĩa, xác định các điểm join point là thực thi phương thức updateProduct()
khi có lời gọi từ phương thức OrderController()
.
execution(* com.pad.loggingwithaop.service.ProductService.updateProduct(..)) && within(com.pad.loggingwithaop.controller.OrderController)
Bạn cũng có thể định nghĩa một pointcut để tái sử dụng.
@Pointcut("execution(* com.pad.loggingwithaop.service.ProductService.getProductById(..))")
public void getProductById(){}
@Before(value = "getProductById()")
public void beforeGetProductById(JoinPoint joinPoint){
log.debug("Get product with id {}", joinPoint.getArgs());
}
@AfterReturning(value = "getProductById()", returning = "result")
public void afterReturnGetProductById(JoinPoint joinPoint, ResponseDTO result){
log.debug("Value of product {} : {}", joinPoint.getArgs(), result);
}
Mã nguồn hoàn chỉnh
package com.pad.loggingwithaop.aspect;
import com.pad.loggingwithaop.model.Product;
import com.pad.loggingwithaop.payload.ResponseDTO;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect
@Component
public class LoggingAspect {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@Before("execution(* com.pad.loggingwithaop.service.ProductService.updateProduct(..)) && within(com.pad.loggingwithaop.repository.*)")
public void logParamBeforeExecuteUpdateProduct(JoinPoint joinPoint){
log.debug("Log before executing method...");
log.debug("This is qualified name: {}", joinPoint.getSignature().getDeclaringTypeName());
log.debug("This is method name: {}", joinPoint.getSignature().getName());
log.debug("Arguments of method: {}", Arrays.toString(joinPoint.getArgs()));
}
@Pointcut("execution(* com.pad.loggingwithaop.service.ProductService.getProductById(..))")
public void getProductById(){}
@Before(value = "getProductById()")
public void beforeGetProductById(JoinPoint joinPoint){
log.debug("Get product with id {}", joinPoint.getArgs());
}
@AfterReturning(value = "getProductById()", returning = "result")
public void afterReturnGetProductById(JoinPoint joinPoint, ResponseDTO result){
log.debug("Value of product {} : {}", joinPoint.getArgs(), result);
}
@AfterThrowing(value = "getProductById()")
public void handleGetProductByIdException(JoinPoint joinPoint){
log.debug("Can not find product with id: {}", joinPoint.getArgs());
}
@Around("execution(* com.pad.loggingwithaop.service.ProductService.deleteProduct(..))")
public void logAfterDeleteProduct(ProceedingJoinPoint joinPoint) throws Throwable{
try{
joinPoint.proceed();
log.debug("Delete product {} successfully", joinPoint.getArgs());
}
catch (Exception e){
log.debug("Can not find product with id: {}", joinPoint.getArgs());
throw e;
}
}
}