在SpringBoot使用AOP

一、AOP是什麼

AOP(Aspect-Oriented Programming,面向方面編程),可以說是OOP(Object-Oriented Programing,面向對象編程)的補充和完善。OOP引入封裝、繼承和多態性等概念來建立一種對象層次結構,用以模擬公共行爲的一個集合。當我們需 要爲分散的對象引入公共行爲的時候,OOP則顯得無能爲力。也就是說,OOP允許你定義從上到下的關係,但並不適合定義從左到右的關係。例如日誌功能。日誌代碼往往水平地散佈在所有對象層次中,而與它所散佈到的對象的核心功能毫無關係。對於其他類型的代碼,如安全性、異常處理和透明的持續性也是如此。這種 散佈在各處的無關的代碼被稱爲橫切(cross-cutting)代碼,在OOP設計中,它導致了大量代碼的重複,而不利於各個模塊的重用。

而AOP技術則恰恰相反,它利用一種稱爲“橫切”的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其名爲 “Aspect”,即方面。所謂“方面”,簡單地說,就是將那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減少系統的重複代碼,降低 模塊間的耦合度,並有利於未來的可操作性和可維護性。AOP代表的是一個橫向的關係,如果說“對象”是一個空心的圓柱體,其中封裝的是對象的屬性和行爲; 那麼面向方面編程的方法,就彷彿一把利刃,將這些空心圓柱體剖開,以獲得其內部的消息。而剖開的切面,也就是所謂的“方面”了。然後它又以巧奪天功的妙手 將這些剖開的切面復原,不留痕跡。

使用“橫切”技術,AOP把軟件系統分爲兩個部分:核心關注點和橫切關注點。業務處理的主要流程是核心關注點,與之關係不大的部分是橫切關注點。橫 切關注點的一個特點是,他們經常發生在覈心關注點的多處,而各處都基本相似。比如權限認證、日誌、事務處理。Aop 的作用在於分離系統中的各種關注點,將核心關注點和橫切關注點分離開來。正如Avanade公司的高級方案構架師Adam Magee所說,AOP的核心思想就是“將應用程序中的商業邏輯同對其提供支持的通用服務進行分離。”

實現AOP的技術,主要分爲兩大類:一是採用動態代理技術,利用截取消息的方式,對該消息進行裝飾,以取代原有對象行爲的執行;二是採用靜態織入的 方式,引入特定的語法創建“方面”,從而使得編譯器可以在編譯期間織入有關“方面”的代碼。然而殊途同歸,實現AOP的技術特性卻是相同的,分別爲:

1、join point(連接點):是程序執行中的一個精確執行點,例如類中的一個方法。它是一個抽象的概念,在實現AOP時,並不需要去定義一個join point。
2、point cut(切入點):本質上是一個捕獲連接點的結構。在AOP中,可以定義一個point cut,來捕獲相關方法的調用。
3、advice(通知):是point cut的執行代碼,是執行“方面”的具體邏輯。
4、aspect(方面):point cut和advice結合起來就是aspect,它類似於OOP中定義的一個類,但它代表的更多是對象間橫向的關係。
5、introduce(引入):爲對象引入附加的方法或屬性,從而達到修改對象結構的目的。有的AOP工具又將其稱爲mixin。

上述的技術特性組成了基本的AOP技術,大多數AOP工具均實現了這些技術。它們也可以是研究AOP技術的基本術語。

二、爲什麼要使用AOP

減少代碼量。提高系統的一致性。使用場景例如:
1、日誌系統
2、消息發佈系統

三、如何使用AOP

1、在pom文件加入依賴

   <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

2、添加配置文件類

package com.xyp.swaggertoexcel.aop;

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 org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * @author xuyuanpeng
 * @version 1.0
 * @date 2019-04-28 17:37
 */

@Aspect
@Component
public class HttpRequestAspect {

    private static final Logger log = LoggerFactory.getLogger(HttpRequestAspect.class);

    public static long startTime;
    public static long endTime;

    /*@PointCut註解表示表示橫切點,哪些方法需要被橫切*/
    /*切點表達式*/
    @Pointcut("execution(public * com.xyp.swaggertoexcel.api.*.*(..))")
    /*切點簽名*/
    public void print() {

    }

    @Around("print()")
    public void around(ProceedingJoinPoint pjp) throws Throwable {
        log.info("around proceed before……");
        pjp.proceed();
        log.info("around proceed after……");
    }

    /*@Before註解表示在具體的方法之前執行*/
    @Before("print()")
    public void before(JoinPoint joinPoint) {
        log.info("前置切面before……");
        startTime = System.currentTimeMillis();
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        String requestURI = request.getRequestURI();
        String remoteAddr = request.getRemoteAddr();   //這個方法取客戶端ip"不夠好"
        String requestMethod = request.getMethod();
        String declaringTypeName = joinPoint.getSignature().getDeclaringTypeName();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        log.info("請求url=" + requestURI + ",客戶端ip=" + remoteAddr + ",請求方式=" + requestMethod + ",請求的類名=" + declaringTypeName + ",方法名=" + methodName + ",入參=" + args);
    }

    /*@After註解表示在方法執行之後執行*/
    @After("print()")
    private void after() {
        endTime = System.currentTimeMillis() - startTime;
        log.info("後置切面after……");
    }

    /*@AfterReturning註解用於獲取方法的返回值*/
    @AfterReturning(pointcut = "print()", returning = "object")
    public void getAfterReturn(Object object) {
        log.info("本次接口耗時={}ms", endTime);
        log.info("afterReturning={}", object.toString());
    }
}

3、輸出

2019-04-28 17:42:21.751 INFO 22620 — [nio-8080-exec-9] c.x.s.aop.HttpRequestAspect : around proceed before……
2019-04-28 17:42:21.751 INFO 22620 — [nio-8080-exec-9] c.x.s.aop.HttpRequestAspect : 前置切面before……
2019-04-28 17:42:21.751 INFO 22620 — [nio-8080-exec-9] c.x.s.aop.HttpRequestAspect : 請求url=/api/http/getDateByUrl,客戶端ip=0:0:0:0:0:0:0:1,請求方式=POST,請求的類名=com.xyp.swaggertoexcel.api.IHttpProxyService,方法名=getDateByUrl,入參=[Ljava.lang.Object;@6890087
2019-04-28 17:42:21.753 INFO 22620 — [nio-8080-exec-9] c.x.s.aop.HttpRequestAspect : around proceed after……
2019-04-28 17:42:21.753 INFO 22620 — [nio-8080-exec-9] c.x.s.aop.HttpRequestAspect : 後置切面after……
2019-04-28 17:42:21.753 INFO 22620 — [nio-8080-exec-9] c.x.s.aop.HttpRequestAspect : 本次接口耗時=2ms

輸出順序
1、around 方法中的 調用pjp.proceed();之前
2、before方法
3、正式的請求切面的方法
4、around 方法中的 調用pjp.proceed();之後
5、after方法

四、參考

https://www.cnblogs.com/jingzhishen/p/4980551.html
https://blog.csdn.net/fanrenxiang/article/details/80844077

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章