Spring框架之Spring AOP Logging教程
在這個教程中,我們將一步一步的教大家使用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 http://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]
「其他文章」
- 一文給你搞定Elasticsearch技術掃盲
- Go程式語言的真正優點是什麼?
- 用Python爬了我的微信好友,他們是這樣的...
- 位元組面試也會問SPI機制?
- Volatile關鍵字能保證原子性麼?
- 種草 Vue3 中幾個好玩的外掛和配置
- Vue 狀態管理未來樣子
- 一門語言的作用域和函式呼叫是如何實現的
- 關於多執行緒同步的一切:偽共享
- Swift 與 Go:蘋果與谷歌的較量
- Android 自定義View - 柱狀波形圖 wave view
- Android技術分享|【Android踩坑】懷疑人生,主執行緒修改UI也會崩潰?
- 安卓TV外掛化9.0內聯崩潰原因及解決方案
- 測試員進階技能:如何有效地利用單元測試報告?
- Tekton 實戰完整示例
- 位元組的前端監控 SDK 是怎樣設計的
- 用Python繪製了若干張詞雲圖,驚豔了所有人
- 馬化騰:為什麼你們不在乎QQ等級,不用QQ了嗎?
- 幾個友好Java程式碼習慣建議
- Python 實現單例模式的五種寫法