spring boot AOP筆記

面向切面編程(AOP)是經過另外一種思考方式來對面向對象編程(OOP)的補充。在抽象的結構中,OOP模塊的基本單元是類,而AOP的基本單元是面。AOP的面可以跨越多個類型和對象來達成模塊化。java

下面是根據個人理解畫的圖:spring

這是一個簡單的MVC結構,不一樣的模塊之間根據類來分離。可是AOP的切面卻能夠跨越多個模塊。圖中的示例表示UserAOP跨越了整個Controller模塊。固然他也能夠同時跨越Model模塊。這取決於AOP的實際業務需求。

AOP提供了一個不一樣的編程思路,不過springIoc並無依賴AOP,對於Ioc來講,AOP能夠提供支持但不是必須。編程

AOP結構

  • 切面(Aspect):跨越多個類別的關注點的模塊化。事務管理是企業Java應用程序中對切面應用最多的例子。在SpringAOP中,切面是使用常規類(基於模式的方法)或使用@Aspect註釋(@AspectJ樣式)註釋的常規類來實現的 。
  • 鏈接點(JoinPoint):程序執行期間的一個點。在springAOP中表示AOP程序執行的點。
  • 通知(Advice):鏈接點在特定狀態下執行的操做。其中包括了「around」、「before」和「after」等不一樣類型的通知。許多AOP框架(包括Spring)都是以攔截器作通知模型,並維護一個以鏈接點爲中心的攔截器鏈。
  • 切入點(Pointcut):和鏈接點匹配的斷言。切入點肯定了AOP程序的入口,並在這以後和鏈接點相連並執行對應的鏈接點方法。切入點表達式如何和鏈接點匹配是AOP的核心:Spring默認使用AspectJ切入點表達式語言。
  • 引入(Introduction):用來給一個類型聲明額外的方法或屬性(也被稱爲鏈接類型聲明(inter-type declaration))。Spring容許引入新的接口(以及一個對應的實現)到任何被代理的對象。例如,你可使用引入來使一個bean實現IsModified接口,以便簡化緩存機制。
  • 目標對象(Target Object):被一個或者多個切面所通知的對象。也被稱作被通知(advised)對象。既然Spring AOP是經過運行時代理實現的,這個對象永遠是一個被代理(proxied)對象。
  • AOP代理(AOP Proxy):AOP框架建立的對象,用來實現切面契約(例如通知方法執行等等)。在Spring中,AOP代理能夠是JDK動態代理或者CGLIB代理。
  • 織入(Weaving):把切面鏈接到其它的應用程序類型或者對象上,並建立一個被通知的對象。這些能夠在編譯時(例如使用AspectJ編譯器),類加載時和運行時完成。Spring和其餘純Java AOP框架同樣,在運行時完成織入。

其中切入點能夠和鏈接點合併,直接在通知中表示:
@Before("execution(com.dust.controller..(..))")
public void before() {/* 方法體 /}
該寫法等價於:
@Pointcut("execution(com.dust.controller.
.(..))")
public void point(){}
@Befor("point()")
public void before(){/
方法體 */}api

通知

在AOP中,鏈接點與切入點的關聯關係以及相應的判斷規則是AOP的核心。切入點肯定了AOP入口,可是具體要執行哪部分則由鏈接點決定。鏈接點是方法執行的入口。數組

  • @Before:前置通知,在鏈接點前執行。這個通知不能阻礙通知以前的程序執行。
@Before("execution(* com.dust.controller.*.*(..))")
    public void beforController() {
        //在Controller被調用前
    }
複製代碼
  • @AfterReturning:後置通知,在鏈接點以後執行,可是能夠獲取到鏈接點的返回值。

這裏能夠知道,對AOP來講,最小的單元是方法。AOP只能在方法和方法之間切入,而不能切入方法自己緩存

@AfterReturning("execution(* com.dust.controller.*.*(..))")
    public void afterController() {
        //在Controller執行完以後
    }
複製代碼

對方法返回值的獲取:併發

@AfterReturning(
        pointcut = "execution(* com.dust.controller.*.*(..))",
        returning = "retVal")
    public void afterController(Object retVal) {
        //對方法返回值的獲取
        System.out.println(retVal.toString());
    }
複製代碼

其中returning中的參數名稱必需要和advice方法的參數名稱相同,當方法執行返回時,返回值將做爲相應的參數值傳遞給advice方法。若是方法沒有返回值,則該參數爲null。app

  • @AfterThrowing:異常通知,當方法拋出異常的時候執行。
@AfterThrowing("execution(* com.dust.controller.*.*(..))")
    public void afterController() {
        //在Controller拋出異常後執行
    }
複製代碼

也能夠設置到拋出給定異常時才執行advice。框架

@AfterThrowing(
        pointcut = "execution(* com.dust.controller.*.*(..))",
        throwing = "ex")
    public void afterController(NullPointerException ex) {
        //拋出空指針異常時執行advice
    }
複製代碼
  • @After:最終通知,在鏈接點以後執行。不論返回點是正常仍是異常。一般用來釋放資源。一般在這裏須要進行正常和異常的返回條件。
@After("execution(* com.dust.controller.*.*(..))")
    public void afterController() {
        //最終執行通知
    }
複製代碼
  • @Around:環繞通知。包圍一個鏈接點的通知,如方法調用。這是最強大的一種通知類型。環繞通知能夠在方法調用先後完成自定義的行爲。他能夠肯定方法什麼時候、如何甚至是否執行。環繞通知使用一個代理ProceedingJoinPoint類型的對象來管理目標對象,因此此通知的第一個參數必須是ProceedingJoinPoint類型,在通知體內,調用ProceedingJoinPoint的proceed()方法會致使後臺的鏈接點方法執行。proceed 方法也可能會被調用而且傳入一個Object[]對象-該數組中的值將被做爲方法執行時的參數。

通知參數

全部通知方法均可以聲明一個類行爲org.aspectj.lang.JoinPoint的參數。模塊化

環繞通知聲明的參數爲ProceedingJoinPoint,由於須要執行ProceedingJoinPoint的proceed()方法。而其餘的通知則不須要。

JoinPoint提供了不少有用的方法:

  • getArgs()(返回方法參數):返回該方法的參數集合,是一組Object[]
  • getThis()(返回代理對象):獲取代理對象自己
  • getTarget()(返回目標):獲取鏈接點所在的目標對象
  • getSignature()(返回正在被通知的方法相關信息):獲取鏈接點的方法簽名對象
  • toString()(打印出正在被通知的方法的有用信息)

傳入參數

一般通知方法獲取方法參數除了上述經過JoinPoint獲取外還能夠經過pointcut獲取

@Pointcut("execution(* com.example.springdemo.controller.*.*(..)) && args(address, text, ..)")
    public void inController(String address, String text) {}

    @Before("inController(address, text)")
    public void beforController(String address, String text) {
        System.out.println("在執行控制器以前,獲取參數{address:" + address + ",text:" + text + "}");
    }
複製代碼

args(address, text, ..)切入點表達式匹配切入點方法的參數,然後傳入給advice方法。 這是要求全部和該切入點匹配的鏈接點都須要接收參數,還能夠單單在鏈接點接收參數:

@Pointcut("execution(* com.example.springdemo.controller.*.*(..))")
    public void inController() {}

    @Before("inController() && args(address, text, ..)")
    public void beforController(String address, String text) {
        System.out.println("在執行控制器以前,獲取參數{address:" + address + ",text:" + text + "}");
    }
複製代碼

其餘參數:代理對象(this),目標對象(target)和註釋(@within, @target, @annotation, @args)均可以以相似的方式綁定。

例子:使用AOP來重試事務

因爲併發問題:死鎖。可能致使業務執行失敗。下次執行又有可能執行成功,所以對於這種操做不但願將重試交給用戶來執行,這個能夠交由系統來執行,這樣對於用戶來講他仍是一次就執行成功了。

因爲要嘗試執行屢次process(),所以使用@Around環繞通知

@Aspect
@Configuration
public class AOPRedo {

    //默認最大重試次數
    private static final int DEFAULT_MAX_RETRIES = 2;

    private int maxRetries = DEFAULT_MAX_RETRIES;

    public void setMaxRetries(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    @Around("execution(* com.example.springdemo.controller.RestAPIController.*(..))")
    public Object apiAroundController(ProceedingJoinPoint pjp) {
        int num = 0;
        Throwable throwable;
        do {
            num++;
            try {
                return pjp.proceed();
            } catch (Throwable th) {
                System.out.println("嘗試捕獲");
                throwable = th;
            }
        } while (num <= maxRetries);
        return null;
    }

}
複製代碼

其中重試次數能夠交給配置文件,經過@PropertySource來導入配置信息。

@RestController
@RequestMapping("api")
public class RestAPIController {

    @Autowired
    EmailService emailService;

    private int count = 0;

    @RequestMapping("email")
    public String email(String address, String info) throws NullPointerException {
        if (count++ < 1) {
            throw new NullPointerException();
        }
        return emailService.setEmail(address,info) + "執行次數:" + count;
    }

}
複製代碼

執行結果

相關文章
相關標籤/搜索