spring總結-----Spring AOP

什麼是AOP

aop(Aspect-Oriented Programming), 即 面向切面編程 , 它與 OOP( Object-Oriented Programming, 面向對象編程) 相輔相成, 提供了與 OOP 不同的抽象軟件結構的視角. 在 OOP 中, 我們以類(class)作爲我們的基本單元, 而 AOP 中的基本單元是 Aspect(切面)

術語

Aspect(切面)

   aspect 由 point cut 和 advice 組成,它既包含了橫切邏輯的定義, 也包括了連接點(join point )的定義. 

  Spring AOP就是負責實施切面的框架, 它將切面所定義的橫切邏輯織入到切面所指定的連接點中. AOP的工作重心在於如何將增    強(advice)織入目標對象Target的連接點上, 這裏包含兩個工作:

  1. 如何通過 pointcut 和 advice 定位到特定的 joinpoint 上

  2. 如何在 advice 中編寫切面代碼.

可以簡單地認爲, 使用 @Aspect 註解的類就是切面,滿足 pointcut 規則的 joinpoint 會被添加相應的 advice 操作.

advice(增強)

由 aspect 添加到特定的 join point(即滿足 point cut 規則的 join point) 的一段代碼. 許多 AOP框架, 包括 Spring AOP, 會將 advice 模擬爲一個攔截器(interceptor), 並且在 join point 上維護多個 advice, 進行層層攔截。

連接點(join point)

程序執行過程中的一個點,如方法的執行或異常的處理。在Spring AOP中,連接點總是表示方法執行。

切點(point cut)

匹配 join point 的謂詞(a predicate that matches join points). Advice 是和特定的 point cut 關聯的, 並且在 point cut 相匹配的 join point 中執行.在 Spring 中, 所有的方法都可以認爲是 joinpoint, 但是我們並不希望在所有的方法上都添加 Advice, 而 pointcut 的作用就是提供一組規則(使用 AspectJ pointcut expression language 來描述) 來匹配joinpoint, 給滿足規則的 joinpoint 添加 Advice.

在 Spring AOP 中, 所有的方法執行都是 join point. 而 point cut 是一個描述信息, 它修飾的是 join point, 通過 point cut, 我們就可以確定哪些 join point 可以被織入 Advice. 因此 join point 和 point cut 本質上就是兩個不同緯度上的東西.advice 是在 join point 上執行的, 而 point cut 規定了哪些 join point 可以執行哪些 advice

目標對象(Target)

織入 advice 的目標對象. 目標對象也被稱爲 advised object.因爲 Spring AOP 使用運行時代理的方式來實現 aspect, 因此 adviced object 總是一個代理對象(proxied object)注意, adviced object 指的不是原來的類, 而是織入 advice 後所產生的代理類.

織入(Weaving)

將 aspect 和其他對象連接起來, 並創建 adviced object 的過程. 根據不同的實現技術, AOP織入有三種方式:

  • 編譯器織入, 這要求有特殊的Java編譯器.

  • 類裝載期織入, 這需要有特殊的類裝載器.

  • 動態代理織入, 在運行期爲目標類添加增強(Advice)生成子類的方式. Spring 採用動態代理織入, 而AspectJ採用編譯器織入和類裝載期織入.

advice 的類型

  • before advice, 在 join point 前被執行的 advice. 雖然 before advice 是在 join point 前被執行, 但是它並不能夠阻止 join point 的執行, 除非發生了異常(即我們在 before advice 代碼中, 不能人爲地決定是否繼續執行 join point 中的代碼)

  • after return advice, 在一個 join point 正常返回後執行的 advice

  • after throwing advice, 當一個 join point 拋出異常後執行的 advice

  • after(final) advice, 無論一個 join point 是正常退出還是發生了異常, 都會被執行的 advice.

  • around advice, 在 join point 前和 joint point 退出後都執行的 advice. 這個是最常用的 advice.

joinpoint 可以認爲是一個 賓語, 而 pointcut 則可以類比爲修飾 joinpoint 的定語, 那麼整個 aspect 就可以描述爲: 滿足 pointcut 規則的 joinpoint 會被添加相應的 advice 操作.

introduction

爲一個類型添加額外的方法或字段. Spring AOP 允許我們爲 目標對象 引入新的接口(和對應的實現). 例如我們可以使用 introduction 來爲一個 bean 實現 IsModified 接口, 並以此來簡化 caching 的實現.

AOP proxy

一個類被 AOP 織入 advice, 就會產生一個結果類, 它是融合了原類和增強邏輯的代理類. 在 Spring AOP 中, 一個 AOP 代理是一個 JDK 動態代理對象或 CGLIB 代理對象.

Spring AOP 默認使用標準的 JDK 動態代理(dynamic proxy)技術來實現 AOP 代理, 通過它, 我們可以爲任意的接口實現代理.如果需要爲一個類實現代理, 那麼可以使用 CGLIB 代理. 當一個業務邏輯對象沒有實現接口時, 那麼Spring AOP 就默認使用 CGLIB 來作爲 AOP 代理了. 即如果我們需要爲一個方法織入 advice, 但是這個方法不是一個接口所提供的方法, 則此時 Spring AOP 會使用 CGLIB 來實現動態代理. 鑑於此, Spring AOP 建議基於接口編程, 對接口進行 AOP 而不是類.

@AspectJ 支持

@AspectJ 是一種使用 Java 註解來實現 AOP 的編碼風格. @AspectJ 風格的 AOP 是 AspectJ Project 在 AspectJ 5 中引入的, 並且 Spring 也支持@AspectJ 的 AOP 風格

使用 Java Configuration 方式使能@AspectJ

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}

使用 XML 方式使能@AspectJ

<aop:aspectj-autoproxy/>

定義 aspect(切面)

當使用註解 @Aspect 標註一個 Bean 後, 那麼 Spring 框架會自動收集這些 Bean, 並添加到 Spring AOP 中, 例如:

@Component
@Aspect
public class MyTest {
}

聲明 pointcut:

一個 pointcut 的聲明由兩部分組成:

  • 一個方法簽名, 包括方法名和相關參數

  • 一個 pointcut 表達式, 用來指定哪些方法執行是我們感興趣的(即因此可以織入 advice).

在@AspectJ 風格的 AOP 中, 我們使用一個方法來描述 pointcut, 即:

@Pointcut("execution(* com.xys.service.UserService.*(..))") // 切點表達式
private void dataAccessOperation() {} // 切點前面

這個方法必須無返回值.這個方法本身就是 pointcut signature, pointcut 表達式使用@Pointcut 註解指定.上面我們簡單地定義了一個 pointcut, 這個 pointcut 所描述的是: 匹配所有在包com.xys.service.UserService 下的所有方法的執行.

聲明 pointcut

一個 pointcut 的聲明由兩部分組成:

  • 一個方法簽名, 包括方法名和相關參數

  • 一個 pointcut 表達式, 用來指定哪些方法執行是我們感興趣的(即因此可以織入 advice).

在@AspectJ 風格的 AOP 中, 我們使用一個方法來描述 pointcut, 即:

@Pointcut("execution(* com.xys.service.UserService.*(..))") // 切點表達式
private void dataAccessOperation() {} // 切點前面

這個方法必須無返回值.這個方法本身就是 pointcut signature, pointcut 表達式使用@Pointcut 註解指定.上面我們簡單地定義了一個 pointcut, 這個 pointcut 所描述的是: 匹配所有在包com.xys.service.UserService 下的所有方法的執行.

切點標誌符(designator)

AspectJ5 的切點表達式由標誌符(designator)和操作參數組成. 如 "execution( greetTo(..))" 的切點表達式, execution 就是 標誌符, 而圓括號裏的 greetTo(..) 就是操作參數

execution

匹配 join point 的執行, 例如 "execution(* hello(..))" 表示匹配所有目標類中的 hello() 方法. 這個是最基本的 pointcut 標誌符.

within

匹配特定包下的所有 join point, 例如 within(com.xys.*) 表示 com.xys 包中的所有連接點, 即包中的所有類的所有方法. 而 within(com.xys.service.*Service) 表示在 com.xys.service 包中所有以 Service 結尾的類的所有的連接點.

this 與 target

this 的作用是匹配一個 bean, 這個 bean(Spring AOP proxy) 是一個給定類型的實例(instance of). 而 target 匹配的是一個目標對象(target object, 即需要織入 advice 的原始的類), 此對象是一個給定類型的實例(instance of).

bean

匹配 bean 名字爲指定值的 bean 下的所有方法, 例如:

bean(*Service) // 匹配名字後綴爲 Service 的 bean 下的所有方法
bean(myService) // 匹配名字爲 myService 的 bean 下的所有方法

args

匹配參數滿足要求的的方法. 例如:

@Pointcut("within(com.xys.demo2.*)")
public void pointcut2() {
}

@Before(value = "pointcut2()  &&  args(name)")
public void doSomething(String name) {
    logger.info("---page: {}---", name);
}
@Service
public class NormalService {
    private Logger logger = LoggerFactory.getLogger(getClass());

    public void someMethod() {
        logger.info("---NormalService: someMethod invoked---");
    }


    public String test(String name) {
        logger.info("---NormalService: test invoked---");
        return "服務一切正常";
    }
}

當 NormalService.test 執行時, 則 advice doSomething 就會執行, test 方法的參數 name 就會傳遞到 doSomething 中.

常用例子:

// 匹配只有一個參數 name 的方法
@Before(value = "aspectMethod()  &&  args(name)")
public void doSomething(String name) {
}

// 匹配第一個參數爲 name 的方法
@Before(value = "aspectMethod()  &&  args(name, ..)")
public void doSomething(String name) {
}

// 匹配第二個參數爲 name 的方法
Before(value = "aspectMethod()  &&  args(*, name, ..)")
public void doSomething(String name) {
}

@annotation

匹配由指定註解所標註的方法, 例如:

@Pointcut("@annotation(com.xys.demo1.AuthChecker)")
public void pointcut() {
}

則匹配由註解 AuthChecker 所標註的方法.

常見的切點表達式

匹配方法簽名

// 匹配指定包中的所有的方法
execution(* com.xys.service.*(..))

// 匹配當前包中的指定類的所有方法
execution(* UserService.*(..))

// 匹配指定包中的所有 public 方法
execution(public * com.xys.service.*(..))

// 匹配指定包中的所有 public 方法, 並且返回值是 int 類型的方法
execution(public int com.xys.service.*(..))

// 匹配指定包中的所有 public 方法, 並且第一個參數是 String, 返回值是 int 類型的方法
execution(public int com.xys.service.*(String name, ..))

匹配類型簽名

// 匹配指定包中的所有的方法, 但不包括子包
within(com.xys.service.*)

// 匹配指定包中的所有的方法, 包括子包
within(com.xys.service..*)

// 匹配當前包中的指定類中的方法
within(UserService)


// 匹配一個接口的所有實現類中的實現的方法
within(UserDao+)

匹配 Bean 名字

// 匹配以指定名字結尾的 Bean 中的所有方法
bean(*Service)

切點表達式組合

// 匹配以 Service 或 ServiceImpl 結尾的 bean
bean(*Service || *ServiceImpl)

// 匹配名字以 Service 結尾, 並且在包 com.xys.service 中的 bean
bean(*Service) && within(com.xys.service.*)

聲明 advice

advice 是和一個 pointcut 表達式關聯在一起的, 並且會在匹配的 join point 的方法執行的前/後/周圍 運行. pointcut 表達式可以是簡單的一個 pointcut 名字的引用, 或者是完整的 pointcut 表達式. 下面我們以幾個簡單的 advice 爲例子, 來看一下一個 advice 是如何聲明的.

Before advice

/**
 * @author xiongyongshun
 * @version 1.0
 * @created 16/9/9 13:13
 */
@Component
@Aspect
public class BeforeAspectTest {
    // 定義一個 Pointcut, 使用 切點表達式函數 來描述對哪些 Join point 使用 advise.
    @Pointcut("execution(* com.xys.service.UserService.*(..))")
    public void dataAccessOperation() {
    }
}
@Component
@Aspect
public class AdviseDefine {
    // 定義 advise
    @Before("com.xys.aspect.PointcutDefine.dataAccessOperation()")
    public void doBeforeAccessCheck(JoinPoint joinPoint) {
        System.out.println("*****Before advise, method: " + joinPoint.getSignature().toShortString() + " *****");
    }
}

這裏, @Before 引用了一個 pointcut, 即 "com.xys.aspect.PointcutDefine.dataAccessOperation()" 是一個 pointcut 的名字. 如果我們在 advice 在內置 pointcut, 則可以:

@Component
@Aspect
public class AdviseDefine {
    // 將 pointcut 和 advice 同時定義
    @Before("within(com.xys.service..*)")
    public void doAccessCheck(JoinPoint joinPoint) {
        System.out.println("*****doAccessCheck, Before advise, method: " + joinPoint.getSignature().toShortString() + " *****");
    }
}

around advice

around advice 比較特別, 它可以在一個方法的之前之前和之後添加不同的操作, 並且甚至可以決定何時, 如何, 是否調用匹配到的方法.

@Component
@Aspect
public class AdviseDefine {
    // 定義 advise
    @Around("com.xys.aspect.PointcutDefine.dataAccessOperation()")
    public Object doAroundAccessCheck(ProceedingJoinPoint pjp) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // 開始
        Object retVal = pjp.proceed();
        stopWatch.stop();
        // 結束
        System.out.println("invoke method: " + pjp.getSignature().getName() + ", elapsed time: " + stopWatch.getTotalTimeMillis());
        return retVal;
    }
}

around advice 和前面的 before advice 差不多, 只是我們把註解 @Before 改爲了@Around 了.

 

 

 

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