AOP中的一些重要術語簡介

2023-03-02 21:01:07

  AOP的定義:AOP(Aspect Oriented Progamming)利用稱為"橫切"的技術,剖解開封裝的物件內部,把多個類的公共行為封裝到一個可重用模組中,便於減少重複程式碼,降低模組之間的耦合度,符合「開閉原則」。

  上面這段關於AOP的定義是從網上抄的,是不是很拗口,我們就結合實際開發來先簡單瞭解下AOP中的一些專業術語,然後再去學習使用它吧。

  在通常的業務開發中,我們往往只需要讓業務邏輯去關注業務本身,但是一些複雜的業務我們為了保證資料的完整、後序發生問題之後能夠快速定位問題,通常伴隨著安全、紀錄檔、事務等程式碼,業務寫多了我們發現這些程式碼通常都是類似的,那麼我們不就可以將這些類似的程式碼封裝起來,再需要使用的地方進行復用嗎?AOP就為提供了這麼一種思路,但是AOP中的一些專業術語特別拗口,我們在這簡單總結下,做下記錄方便後面的學習。

  1、通知(Advice):就是我們上面提到的除了業務程式碼本身之外,一些「可重用的一些程式碼」,先定義好,在需要的地方進行重用;

  2、連線點(JoinPoint):允許你使用「通知」的地方,就是允許你重複使用程式碼的地方,方法執行的前後或者方法丟擲異常時,Spring只支援方法級別的連線點;

  3、切入點(Pointcut): 假如有若干方法,但是我們並不是想在所有的方法中「重用這段可複用的程式碼」,我們只想在某些特定的方法上使用通知,那麼我們就可以使用切入點來進行這些連線點的「篩選了」;

  4、切面(Aspect):切面簡單來講就是切入點(JoinPoint)和通知(Advice)的結合體。通知(Advice)決定了要幹什麼(通知的方法體)?在什麼時候幹?(定義通知的註解型別)。而切入點決定了要在哪兒幹(即執行通知定義的方法體)。

  5、引入(Introduction):允許我們向目標物件新增新的方法屬性(即通過執行通知來控制對目標方法的存取);

  6、目標物件(Target):引入中所提到的目標物件,也就是要被通知的物件,也就是執行真正的業務邏輯,可以在毫不知情的情況下,織入我們的切面;

  7、代理(Proxy):Spring中的AOP都是通過動態代理來實現的,關於代理有不明白的可以參考設計模式之(8)——代理模式

    8、織入(Weaving):把切面應用到目標物件,建立代理物件的過程;

    9、AOP方法:通知+目標物件的方法。

  上面就是AOP程式設計中一些常用的屬於,然後我們再看看AOP中最重要的通知(Advice)的分類,通知分為五類:

  為了方便下面理解,我們可以認為「連線點就是一個目標方法」。

  1、前置通知(Before Advice):在目標方法之前執行,前置通知不會影響目標方法的執行,除非前置通知丟擲異常;

  2、正常返回通知(After Advice):在目標方法正常執行完成之後執行,如果目標方法丟擲異常,則不會執行;

  3、異常返回通知(AfterThrowing Advice):在目標方法丟擲異常之後會執行;

  4、返回通知(AfterReturning Advice):在目標方法執行完之後執行,不管目標方法是正常執行完,還是因為丟擲異常退出,都會執行返回通知內的內容;

  5、環繞通知(Around Advice):環繞通知圍繞在目標方法執行的前後,是一個功能最為強大的通知型別,可以在方法執行前後自定義一些操作,環繞通知還需要負責決定是繼續處理join point(呼叫ProceedingJoinPoint的proceed方法)還是中斷執行,為了避免後面的空指標,環繞通知還需要返回方法的執行結果。

  以下是我寫的一個簡單範例,通過一個自定義註解,來向需要的地方織入通知;

  自定義註解:

package com.pep.process.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @ClassName: Audit
 * @Description: 描述這個類的作用
 * @author: wwh
 * @date: 2023年3月2日 上午9:47:52
 */

@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Audit {
    
    String action() default "";

    String name() default "";
}

  定義一個切面,切面中包含切入點和通知:

package com.pep.process.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * @ClassName: AuditAop
 * @Description: 自定義切面
 * @author: wwh
 * @date: 2023年3月2日 上午9:54:48
 */
@Component
@Aspect
public class AuditAspect {
    
    /**
     * @Title: audit
     * @Description: 定義一個切入點
     * void 返回型別
     */
    @Pointcut("@annotation(com.pep.process.annotation.Audit)")
    private void audit() {
        
    }
    
    /**
     * @Title: before
     * @Description: 前置通知
     * void 返回型別
     */
    @Before("audit()")
    public void before() {
        System.err.println("我是前置通知...");
    }
    
    /**
     * @Title: around
     * @Description: 環繞通知
     * @param joinPoint
     * @return 
     * Object 返回型別
     */
    @Around("audit()")
    public Object around(ProceedingJoinPoint joinPoint) {
        try {
            System.err.println("環繞通知執行前...");
            Object proceed = joinPoint.proceed();
            System.err.println("環繞通知執行後...");
            return proceed;
        } catch (Throwable e) {
            e.printStackTrace();
            System.err.println("目標方法執行過程中發生了異常...");
            return null;
        }
    }
    
    /**
     * @Title: after
     * @Description: 描述這個方法的作用 
     * void 返回型別
     */
    @After(value="audit()")
    public void after() {
        System.err.println("我是後置通知...");
    }
    
    /**
     * @Title: afterThrow
     * @Description: 返回異常通知
     * @param ex 
     * void 返回型別
     */
    @AfterThrowing(value="audit()",throwing="ex")
    public void afterThrow(Exception ex) {
        System.err.println("異常通知...");
    }
    
    /**
     * @Title: afterReturning
     * @Description: 返回通知
     * @param obj
     * @return 
     * Object 返回型別
     */
    @AfterReturning(value="audit()",returning="obj")
    public Object afterReturning(Object obj) {
        System.err.println("我是返回通知...");
        return obj;
    }    
}

  需要注意的是異常通知的註解中的throwing引數,通過這個引數,可以目標方法執行過程中丟擲的異常,繫結到異常通知的方法引數中,並且這個引數值要和方法引數名稱一致。

  同理返回通知註解中的returning引數,也是將目標方法的執行結果和返回通知中方法的引數進行繫結,名稱也必須一致。

  通過使用自定義的方式來向需要的地方織入通知,實現方法功能「增強」或者控制方法存取。

package com.pep.process.controller;

import net.sf.json.JSONObject;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.pep.jxw.common.util.UUIDGenerator;
import com.pep.process.annotation.Audit;

@Controller
public class LogController {
    
    @RequestMapping("/test.do")
    @Audit
    @ResponseBody
    public JSONObject log() {
        JSONObject json = new JSONObject();
        json.put("id", UUIDGenerator.getUUID());
        System.err.println("執行了目標方法...");
        return json;
    }
}

  輸出結果如下:

  

 

   相信通過上面這個返回結果的分析,我們對各種通知的執行順序有一個簡單瞭解,後續的一些問題我們有空再議。

  參考文章:

  1、https://blog.csdn.net/u011402896/article/details/80369220

  2、https://zhuanlan.zhihu.com/p/610434341