Spring框架之Spring AOP Logging教程

开发 架构
我们将一步一步的教大家使用Spring AOP实现一个记录service、controller、repository日志的Aspect。

在这个教程中,我们将一步一步的教大家使用Spring AOP实现一个记录service、controller、repository日志的Aspect。

Maven dependencies - pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.whu</groupId>
<artifactId>aop</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>aop</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

Domain层

创建一个简单的Employee实体类:

package com.whu.aop.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "employees")
public class Employee {
private long id;
private String firstName;
private String lastName;
private String emailId;
public Employee() {
}
public Employee(long id, String firstName, String lastName, String emailId) {
super();
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.emailId = emailId;
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
@Column(name = "first_name", nullable = false)
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
@Column(name = "last_name", nullable = false)
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
@Column(name = "email_address", nullable = false)
public String getEmailId() {
return emailId;
}
public void setEmailId(String emailId) {
this.emailId = emailId;
}
@Override
public String toString() {
return "Employee [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + ", emailId=" + emailId +
"]";
}
}

Repository层

package com.whu.aop.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.whu.aop.model.Employee;
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}

Service层

package com.whu.aop.service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.whu.aop.exception.ResourceNotFoundException;
import com.whu.aop.model.Employee;
import com.whu.aop.repository.EmployeeRepository;
@Service
public class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
public List<Employee> getAllEmployees() {
return employeeRepository.findAll();
}
public Optional<Employee> getEmployeeById(Long employeeId)
throws ResourceNotFoundException {
return employeeRepository.findById(employeeId);
}
public Employee createEmployee(Employee employee) {
return employeeRepository.save(employee);
}
public Employee updateEmployee(Long employeeId,
Employee employeeDetails) throws ResourceNotFoundException {
Employee employee = employeeRepository.findById(employeeId)
.orElseThrow(()-> new ResourceNotFoundException("Employee not found for this id ::"+employeeId));
employee.setEmailId(employeeDetails.getEmailId());
employee.setLastName(employeeDetails.getLastName());
employee.setFirstName(employeeDetails.getFirstName());
final Employee updatedEmployee = employeeRepository.save(employee);
return updatedEmployee;
}
public Map<String, Boolean> deleteEmployee(Long employeeId)
throws ResourceNotFoundException {
Employee employee = employeeRepository.findById(employeeId)
.orElseThrow(()-> new ResourceNotFoundException("Employee not found for this id :: "+employeeId));
employeeRepository.delete(employee);
Map <String, Boolean > response = new HashMap<>();
response.put("deleted", Boolean.TRUE);
return response;
}
}

Controller层

package com.whu.aop.controller;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.whu.aop.exception.ResourceNotFoundException;
import com.whu.aop.model.Employee;
import com.whu.aop.service.EmployeeService;
@RestController
@RequestMapping("/api/v1")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@GetMapping("/employees")
public List<Employee> getAllEmployees() {
return employeeService.getAllEmployees();
}
@GetMapping("/employees/{id}")
public ResponseEntity<Employee> getEmployeeById(@PathVariable(value = "id") Long employeeId)
throws ResourceNotFoundException {
Employee employee = employeeService.getEmployeeById(employeeId)
.orElseThrow(() ->new ResourceNotFoundException("Employee not found for this id :: "+employeeId));
return ResponseEntity.ok().body(employee);
}
@PostMapping("/employees")
public Employee createEmployee(@Validated @RequestBody Employee employee) {
return employeeService.createEmployee(employee);
}
@PutMapping("/employees/{id}")
public ResponseEntity < Employee > updateEmployee(@PathVariable(value = "id") Long employeeId,
@Validated @RequestBody Employee employeeDetails) throws ResourceNotFoundException {
Employee updatedEmployee = employeeService.updateEmployee(employeeId, employeeDetails);
return ResponseEntity.ok(updatedEmployee);
}
@DeleteMapping("/employees/{id}")
public Map<String, Boolean > deleteEmployee(@PathVariable(value = "id") Long employeeId)
throws ResourceNotFoundException {
return employeeService.deleteEmployee(employeeId);
}
}

至此,一个简单的web应该已经建好,可以通过http://localhost:8080//api/v1/employees 访问第一个接口,请求获取所有employees。

创建Logging Aspect

现在,让我们创建一个Aspect来记录service和repository组件的执行情况。我们将创建4个方法,以下是详细内容:

  • springBeanPointcut()--匹配所有repository、service和Web REST端点的pointcut。
  • applicationPackagePointcut()--用于匹配应用程序主包中的所有Spring Bean的pointcut。
  • logAfterThrowing()--记录抛出异常的方法的advice。
  • logAround()--记录方法进入和退出时的advice。
package com.whu.aop.aspect;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@Pointcut("within(@org.springframework.stereotype.Repository *)" +
" || within(@org.springframework.stereotype.Service *)" +
" || within(@org.springframework.web.bind.annotation.RestController *)")
public void springBeanPointcut() {
// Method is empty as this is just a Pointcut, the implementations are in the advices.
}
/**
* Pointcut that matches all Spring beans in the application's main packages.
*/
@Pointcut(value = "within(com.whu.aop..*)" +
" || within(com.whu.aop.service..*)" +
" || within(com.whu.aop.controller..*)")
public void applicationPackagePointcut() {
}
/**
* Advice that logs method throwing exceptions.
* @param joinpoint
* @param e
*/
@AfterThrowing(pointcut = "applicationPackagePointcut() && springBeanPointcut()", throwing = "e")
public void logAfterThrowing(JoinPoint joinpoint, Throwable e) {
log.error("Exception in {}.{}() with cause = {}",
joinpoint.getSignature().getDeclaringTypeName(),
joinpoint.getSignature().getName(),
e.getCause() != null ? e.getCause() : "NULL");
}

@Around("applicationPackagePointcut() && springBeanPointcut()")
public Object logAround(ProceedingJoinPoint joinpoint) throws Throwable {
if(log.isDebugEnabled()) {
log.debug("Enter: {}.{}() with arguments[s] = {}",
joinpoint.getSignature().getDeclaringType(),
joinpoint.getSignature().getName(),
Arrays.toString(joinpoint.getArgs()));
}
try {
Object result = joinpoint.proceed();
if(log.isDebugEnabled()) {
log.debug("Exit: {}.{}() with result = {}",
joinpoint.getSignature().getDeclaringType(),
joinpoint.getSignature().getName(),
result);
}
return result;
}catch (IllegalArgumentException e) {
log.error("Illegal argument: {} in {}.{}()",
Arrays.toString(joinpoint.getArgs()),
joinpoint.getSignature().getDeclaringType(),
joinpoint.getSignature().getName());
throw e;
}
}
}

application.properties

logging.level.org.springframework.web=INFO
logging.level.org.hibernate=ERROR
logging.level.com.whu=DEBUG

Exception Handling

我们可以用@ResponseStatus注解来指定特定异常的响应状态,以及异常的定义。

package com.whu.aop.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends Exception {
private static final long serialVersionUID = 1L;
public ResourceNotFoundException(String message){
super(message);
}
}

自定义Error Response结构

Spring Boot提供的默认错误响应,通常包含所有需要的detail。但是,你可以创建一个独立于框架的响应结构。在这种情况下,你可以定义一个特定的错误响应结构。让我们来定义一个简单的错误响应Bean。

package com.whu.aop.exception;
import java.util.Date;
public class ErrorDetails {
private Date timestamp;
private String message;
private String details;
public ErrorDetails(Date timestamp, String message, String details) {
super();
this.timestamp = timestamp;
this.message = message;
this.details = details;
}
public Date getTimestamp() {
return timestamp;
}
public String getMessage() {
return message;
}
public String getDetails() {
return details;
}
}
package com.whu.aop.exception;
import java.util.Date;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<?> resourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<?> globleExcpetionHandler(Exception ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
}
}

日志输出

访问http://localhost:8080/api/v1/employees/。

2022-06-04 20:35:22.702 DEBUG 34484 --- [nio-8080-exec-4] com.whu.aop.aspect.LoggingAspect         : Enter: class com.whu.aop.controller.EmployeeController.getAllEmployees() with arguments[s] = []
2022-06-04 20:35:22.702 DEBUG 34484 --- [nio-8080-exec-4] com.whu.aop.aspect.LoggingAspect : Enter: class com.whu.aop.service.EmployeeService.getAllEmployees() with arguments[s] = []
2022-06-04 20:35:22.704 DEBUG 34484 --- [nio-8080-exec-4] com.whu.aop.aspect.LoggingAspect : Exit: class com.whu.aop.service.EmployeeService.getAllEmployees() with result = []
2022-06-04 20:35:22.704 DEBUG 34484 --- [nio-8080-exec-4] com.whu.aop.aspect.LoggingAspect : Exit: class com.whu.aop.controller.EmployeeController.getAllEmployees() with result = []

访问http://localhost:8080/api/v1/employees/1。

2022-06-04 20:36:19.902 DEBUG 34484 --- [nio-8080-exec-6] com.whu.aop.aspect.LoggingAspect         : Enter: class com.whu.aop.controller.EmployeeController.getEmployeeById() with arguments[s] = [1]
2022-06-04 20:36:19.903 DEBUG 34484 --- [nio-8080-exec-6] com.whu.aop.aspect.LoggingAspect : Enter: class com.whu.aop.service.EmployeeService.getEmployeeById() with arguments[s] = [1]
2022-06-04 20:36:19.907 DEBUG 34484 --- [nio-8080-exec-6] com.whu.aop.aspect.LoggingAspect : Exit: class com.whu.aop.service.EmployeeService.getEmployeeById() with result = Optional.empty
2022-06-04 20:36:19.910 ERROR 34484 --- [nio-8080-exec-6] com.whu.aop.aspect.LoggingAspect : Exception in com.whu.aop.controller.EmployeeController.getEmployeeById() with cause = NULL
2022-06-04 20:36:19.918 WARN 34484 --- [nio-8080-exec-6] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [com.whu.aop.exception.ResourceNotFoundException: Employee not found for this id :: 1]
责任编辑:姜华 来源: 今日头条
相关推荐

2022-06-07 07:58:45

SpringSpring AOP

2009-09-29 10:00:40

Spring AOP框

2022-05-30 09:32:07

Spring容器

2009-06-19 13:28:30

Spring AOPSpring 2.0

2011-09-15 10:15:30

Spring

2022-05-27 08:25:55

容器Spring

2021-05-06 18:17:52

SpringAOP理解

2011-05-18 09:47:39

spring

2022-02-17 13:39:09

AOP接口方式

2009-06-22 10:41:34

Spring.AOP

2015-05-06 10:05:22

javajava框架spring aop

2009-06-19 11:09:27

Spring AOP

2023-02-01 09:15:41

2022-02-08 17:07:54

Spring BooSpring Aop日志记录

2021-03-01 23:26:41

日志Spring BootAOP

2024-11-04 16:29:19

2023-03-29 08:24:30

2021-03-30 08:49:27

测试Spring AOP

2022-06-09 07:27:14

JavaSpring容器

2022-05-30 11:17:44

Spring容器配置
点赞
收藏

51CTO技术栈公众号