AOP
1.什么是 AOP ?
AOP 的全稱為 Aspect Oriented Programming,譯為面向切面編程,是通過預編譯方式和運行期動態代理實現核心業務邏輯之外的橫切行為的統一維護的一種技術。AOP 是面向對象編程(OOP)的補充和擴展。
利用 AOP 可以對業務邏輯各部分進行隔離,從而達到降低模塊之間的耦合度,並將那些影響多個類的公共行為封裝到一個可重用模塊,從而到達提高程序的復用性,同時提高了開發效率,提高了系統的可操作性和可維護性。
2.為什么要用 AOP ?
在實際的 Web 項目開發中,我們常常需要對各個層面實現日志記錄,性能統計,安全控制,事務處理,異常處理等等功能。如果我們對每個層面的每個類都獨立編寫這部分代碼,那久而久之代碼將變得很難維護,所以我們把這些功能從業務邏輯代碼中分離出來,聚合在一起維護,而且我們能靈活地選擇何處需要使用這些代碼。
3.AOP 的核心概念
| 名詞 | 概念 | 理解 |
|---|---|---|
| 通知(Advice) | 攔截到連接點之后所要執行的代碼,通知分為前置、后置、異常、最終、環繞通知五類 | 我們要實現的功能,如日志記錄,性能統計,安全控制,事務處理,異常處理等等,說明什么時候要干什么 |
| 連接點(Joint Point) | 被攔截到的點,如被攔截的方法、對類成員的訪問以及異常處理程序塊的執行等等,自身還能嵌套其他的 Joint Point | Spring 允許你用通知的地方,方法有關的前前后后(包括拋出異常) |
| 切入點(Pointcut) | 對連接點進行攔截的定義 | 指定通知到哪個方法,說明在哪干 |
| 切面(Aspect) | 切面類的定義,里面包含了切入點(Pointcut)和通知(Advice)的定義 | 切面就是通知和切入點的結合 |
| 目標對象(Target Object) | 切入點選擇的對象,也就是需要被通知的對象;由於 Spring AOP 通過代理模式實現,所以該對象永遠是被代理對象 | 業務邏輯本身 |
| 織入(Weaving) | 把切面應用到目標對象從而創建出 AOP 代理對象的過程。織入可以在編譯期、類裝載期、運行期進行,而 Spring 采用在運行期完成 | 切點定義了哪些連接點會得到通知 |
| 引入(Introduction ) | 可以在運行期為類動態添加方法和字段,Spring 允許引入新的接口到所有目標對象 | 引入就是在一個接口/類的基礎上引入新的接口增強功能 |
| AOP 代理(AOP Proxy ) | Spring AOP 可以使用 JDK 動態代理或者 CGLIB 代理,前者基於接口,后者基於類 | 通過代理來對目標對象應用切面 |
Spring AOP
1.簡介
AOP 是 Spring 框架中的一個核心內容。在 Spring 中,AOP 代理可以用 JDK 動態代理或者 CGLIB 代理 CglibAopProxy 實現。Spring 中 AOP 代理由 Spring 的 IOC 容器負責生成和管理,其依賴關系也由 IOC 容器負責管理。
2.相關注解
| 注解 | 說明 |
|---|---|
| @Aspect | 將一個 java 類定義為切面類 |
| @Pointcut | 定義一個切入點,可以是一個規則表達式,比如下例中某個 package 下的所有函數,也可以是一個注解等 |
| @Before | 在切入點開始處切入內容 |
| @After | 在切入點結尾處切入內容 |
| @AfterReturning | 在切入點 return 內容之后處理邏輯 |
| @Around | 在切入點前后切入內容,並自己控制何時執行切入點自身的內容 |
| @AfterThrowing | 用來處理當切入內容部分拋出異常之后的處理邏輯 |
| @Order(100) | AOP 切面執行順序, @Before 數值越小越先執行,@After 和 @AfterReturning 數值越大越先執行 |
其中 @Before、@After、@AfterReturning、@Around、@AfterThrowing 都屬於通知(Advice)。
利用 AOP 實現 Web 日志處理
1.構建項目
2.添加依賴
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 熱部署模塊 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional> <!-- 這個需要為 true 熱部署才有效 -->
</dependency>
<!-- Spring AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
3.Web 日志注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ControllerWebLog {
String name();//所調用接口的名稱
boolean intoDb() default false;//標識該條操作日志是否需要持久化存儲
}
4.實現切面邏輯
@Aspect
@Component
@Order(100)
public class WebLogAspect {
private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);
private ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<Map<String, Object>>();
/**
* 橫切點
*/
@Pointcut("execution(public * cn.zwqh.springboot.controller..*.*(..))")
public void webLog() {
}
/**
* 接收請求,並記錄數據
* @param joinPoint
* @param controllerWebLog
*/
@Before(value = "webLog()&& @annotation(controllerWebLog)")
public void doBefore(JoinPoint joinPoint, ControllerWebLog controllerWebLog) {
// 接收到請求
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
// 記錄請求內容,threadInfo存儲所有內容
Map<String, Object> threadInfo = new HashMap<>();
logger.info("URL : " + request.getRequestURL());
threadInfo.put("url", request.getRequestURL());
logger.info("URI : " + request.getRequestURI());
threadInfo.put("uri", request.getRequestURI());
logger.info("HTTP_METHOD : " + request.getMethod());
threadInfo.put("httpMethod", request.getMethod());
logger.info("REMOTE_ADDR : " + request.getRemoteAddr());
threadInfo.put("ip", request.getRemoteAddr());
logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "."
+ joinPoint.getSignature().getName());
threadInfo.put("classMethod",
joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
logger.info("ARGS : " + Arrays.toString(joinPoint.getArgs()));
threadInfo.put("args", Arrays.toString(joinPoint.getArgs()));
logger.info("USER_AGENT"+request.getHeader("User-Agent"));
threadInfo.put("userAgent", request.getHeader("User-Agent"));
logger.info("執行方法:" + controllerWebLog.name());
threadInfo.put("methodName", controllerWebLog.name());
threadLocal.set(threadInfo);
}
/**
* 執行成功后處理
* @param controllerWebLog
* @param ret
* @throws Throwable
*/
@AfterReturning(value = "webLog()&& @annotation(controllerWebLog)", returning = "ret")
public void doAfterReturning(ControllerWebLog controllerWebLog, Object ret) throws Throwable {
Map<String, Object> threadInfo = threadLocal.get();
threadInfo.put("result", ret);
if (controllerWebLog.intoDb()) {
//插入數據庫操作
//insertResult(threadInfo);
}
// 處理完請求,返回內容
logger.info("RESPONSE : " + ret);
}
/**
* 獲取執行時間
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
@Around(value = "webLog()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object ob = proceedingJoinPoint.proceed();
Map<String, Object> threadInfo = threadLocal.get();
Long takeTime = System.currentTimeMillis() - startTime;
threadInfo.put("takeTime", takeTime);
logger.info("耗時:" + takeTime);
threadLocal.set(threadInfo);
return ob;
}
/**
* 異常處理
* @param throwable
*/
@AfterThrowing(value = "webLog()", throwing = "throwable")
public void doAfterThrowing(Throwable throwable) {
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
// 異常信息
logger.error("{}接口調用異常,異常信息{}", request.getRequestURI(), throwable);
}
}
5.測試接口
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/getOne")
@ControllerWebLog(name = "查詢", intoDb = true)
public String getOne(Long id, String name) {
return "1234";
}
}
6.運行測試
瀏覽器請求:http://127.0.0.1:8080/user/getOne?id=1&name=zwqh ,可以看到后台日志輸出:

小結
日志記錄只是一個簡單的示例,而 Spring AOP 的應用讓整個系統變的更加有條不紊,在其他場景應用也很強大。
它幫助我們降低模塊間耦合度,提高程序復用性,提高開發效率,提高系統可做性和可維護性。
示例代碼
非特殊說明,本文版權歸 朝霧輕寒 所有,轉載請注明出處.
原文標題:Spring Boot 2.X(八):Spring AOP 實現簡單的日志切面
原文地址: https://www.zwqh.top/article/info/14
如果文章對您有幫助,請掃碼關注下我的公眾號,文章持續更新中...

