在这个教程中,框架我们将一步一步的框架教大家使用Spring AOP实现一个记录service、controller、框架repository日志的框架Aspect。 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> spring-boot-starter-parent aop spring-boot-starter-actuator spring-boot-starter-data-jpa spring-boot-starter-web spring-boot-devtools h2 spring-boot-starter-test spring-boot-maven-plugin 创建一个简单的框架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 + "]"; } 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 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 return employeeRepository.findAll(); } public Optional 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 throws ResourceNotFoundException { Employee employee = employeeRepository.findById(employeeId) .orElseThrow(()-> new ResourceNotFoundException("Employee not found for this id :: "+employeeId)); employeeRepository.delete(employee); Map response.put("deleted", Boolean.TRUE); return response; } 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 return employeeService.getAllEmployees(); } @GetMapping("/employees/{ id}") public ResponseEntity 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 throws ResourceNotFoundException { return employeeService.deleteEmployee(employeeId); } 至此,一个简单的框架web应该已经建好,可以通过http://localhost:8080//api/v1/employees 访问第一个接口,框架请求获取所有employees。框架 现在,框架让我们创建一个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 applications 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; } } logging.level.org.springframework.web=INFO logging.level.org.hibernate=ERROR 我们可以用@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); } 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 = [] 访问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 = NULLMaven dependencies - pom.xml
Domain层
Repository层
Service层
Controller层
创建Logging Aspect
application.properties
Exception Handling
自定义Error Response结构
日志输出