Spring AOP知識整理

Spring AOP知識整理

Aop是什麼

與OOP對比,面向切面,傳統的OOP開發中的代碼邏輯是自上而下的,而這些過程會產生一些橫切性問題,這些橫切性的問題和我們的主業務邏輯關係不大,這些橫切性問題不會影響到主邏輯實現的,但是會散落到代碼的各個部分,難以維護。AOP是處理一些橫切性問題,AOP的編程思想就是把這些問題和主業務邏輯分開,達到與主業務邏輯解耦的目的。使代碼的重用性和開發效率更高。

aop的應用場景

  1. 日誌記錄

  2. 權限驗證

  3. 效率檢查

  4. 事務管理

  5. exception

springAop的底層技術

JDK動態代理 CGLIB代理
編譯時期的織入還是運行時期的織入? 運行時期織入 運行時期織入
初始化時期織入還是獲取對象時期織入? 初始化時期織入 初始化時期織入

springAop和AspectJ的關係

Aop是一種概念

springAop、AspectJ都是Aop的實現,SpringAop有自己的語法,但是語法複雜,所以SpringAop藉助了AspectJ的註解,但是底層實現還是自己的

spring AOP提供兩種編程風格

@AspectJ support         ------------>利用aspectj的註解

Schema-based AOP support ----------->xml aop:config 命名空間

證明:spring,通過源  碼分析了,我們可以知道spring底層使用的是JDK或者CGLIB來完成的代理,並且在官網上spring給出了aspectj的文檔,和springAOP是不同的

spring Aop的概念

aspect:一定要給spring去管理  抽象  aspectj->類  

pointcut:切點表示連接點的集合  ------------------->           表

  (我的理解:PointCut是JoinPoint的謂語,這是一個動作,主要是告訴通知連接點在哪裏,切點表達式決定 JoinPoint 的數量)

Joinpoint:連接點   目標對象中的方法 ---------------->    記錄

  (我的理解:JoinPoint是要關注和增強的方法,也就是我們要作用的點)

Weaving :把代理邏輯加入到目標對象上的過程叫做織入

target 目標對象 原始對象

aop Proxy 代理對象  包含了原始對象的代碼和增加後的代碼的那個對象

advice:通知    (位置 + logic)

advice通知類型:

Before 連接點執行之前,但是無法阻止連接點的正常執行,除非該段執行拋出異常

After  連接點正常執行之後,執行過程中正常執行返回退出,非異常退出

After throwing  執行拋出異常的時候

After (finally)  無論連接點是正常退出還是異常退出,都會執行

Around advice: 圍繞連接點執行,例如方法調用。這是最有用的切面方式。around通知可以在方法調用之前和之後執行自定義行爲。它還負責選擇是繼續加入點還是通過返回自己的返回值或拋出異常來快速建議的方法執行。

Proceedingjoinpoint 和JoinPoint的區別:

Proceedingjoinpoint 繼承了JoinPoint,proceed()這個是aop代理鏈執行的方法。並擴充實現了proceed()方法,用於繼續執行連接點。JoinPoint僅能獲取相關參數,無法執行連接點。

JoinPoint的方法

1.java.lang.Object[] getArgs():獲取連接點方法運行時的入參列表; 

2.Signature getSignature() :獲取連接點的方法簽名對象; 

3.java.lang.Object getTarget() :獲取連接點所在的目標對象; 

4.java.lang.Object getThis() :獲取代理對象本身;

proceed()有重載,有個帶參數的方法,可以修改目標方法的的參數

Introductions

perthis

使用方式如下:

@Aspect("perthis(this(com.chenss.dao.IndexDaoImpl))")

要求:

\1. AspectJ對象的注入類型爲prototype

\2. 目標對象也必須是prototype的

原因爲:只有目標對象是原型模式的,每次getBean得到的對象纔是不一樣的,由此針對每個對象就會產生新的切面對象,才能產生不同的切面結果。

springAop支持AspectJ

1、啓用@AspectJ支持

使用Java Configuration啓用@AspectJ支持

要使用Java @Configuration啓用@AspectJ支持,請添加@EnableAspectJAutoProxy註釋

@Configuration

@EnableAspectJAutoProxy

public class AppConfig {

}

使用XML配置啓用@AspectJ支持

要使用基於xml的配置啓用@AspectJ支持,可以使用aop:aspectj-autoproxy元素

<aop:aspectj-autoproxy/>

2、聲明一個Aspect

申明一個@Aspect註釋類,並且定義成一個bean交給Spring管理。

@Component

@Aspect

public class UserAspect {

}

3、申明一個pointCut

切入點表達式由@Pointcut註釋表示。切入點聲明由兩部分組成:一個簽名包含名稱和任何參數,以及一個切入點表達式,該表達式確定我們對哪個方法執行感興趣。

@Pointcut("execution(* transfer(..))")// 切入點表達式
private void anyOldTransfer() {}// 切入點簽名

切入點確定感興趣的 join points(連接點),從而使我們能夠控制何時執行通知。Spring AOP只支持Spring bean的方法執行 join points(連接點),所以您可以將切入點看作是匹配Spring bean上方法的執行。

/**

 \* 申明Aspect,並且交給spring容器管理

 */

@Component

@Aspect

public class UserAspect {

​    /**

​     \* 申明切入點,匹配UserDao所有方法調用

​     \* execution匹配方法執行連接點

​     \* within:將匹配限制爲特定類型中的連接點

​     \* args:參數

​     \* target:目標對象

​     \* this:代理對象

​     */

​    @Pointcut("execution(* com.yao.dao.UserDao.*(..))")

​    public void pintCut(){

​        System.out.println("point cut");

​    }

4、申明一個Advice通知

advice通知與pointcut切入點表達式相關聯,並在切入點匹配的方法執行@Before之前、@After之後或前後運行。

/**

 \* 申明Aspect,並且交給spring容器管理

 */

@Component

@Aspect

public class UserAspect {

​    /**

​     \* 申明切入點,匹配UserDao所有方法調用

​     \* execution匹配方法執行連接點

​     \* within:將匹配限制爲特定類型中的連接點

​     \* args:參數

​     \* target:目標對象

​     \* this:代理對象

​     */

​    @Pointcut("execution(* com.yao.dao.UserDao.*(..))")

​    public void pintCut(){

​        System.out.println("point cut");

​    }

​    /**

​     \* 申明before通知,在pintCut切入點前執行

​     \* 通知與切入點表達式相關聯,

​     \* 並在切入點匹配的方法執行之前、之後或前後運行。

​     \* 切入點表達式可以是對指定切入點的簡單引用,也可以是在適當位置聲明的切入點表達式。

​     */

​    @Before("com.yao.aop.UserAspect.pintCut()")

​    public void beforeAdvice(){

​        System.out.println("before");

​    }

}

各種連接點joinPoint的意義:

  1. execution

用於匹配方法執行 join points連接點,最小粒度方法,在aop中主要使用。

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

這裏問號表示當前項可以有也可以沒有,其中各項的語義如下

modifiers-pattern:方法的可見性,如public,protected;

ret-type-pattern:方法的返回值類型,如int,void等;

declaring-type-pattern:方法所在類的全路徑名,如com.spring.Aspect;

name-pattern:方法名類型,如buisinessService();

param-pattern:方法的參數類型,如java.lang.String;

throws-pattern:方法拋出的異常類型,如java.lang.Exception;

example:

@Pointcut("execution(* com.chenss.dao.*.*(..))")//匹配com.chenss.dao包下的任意接口和類的任意方法

@Pointcut("execution(public * com.chenss.dao.*.*(..))")//匹配com.chenss.dao包下的任意接口和類的public方法

@Pointcut("execution(public * com.chenss.dao.*.*())")//匹配com.chenss.dao包下的任意接口和類的public 無方法參數的方法

@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String, ..))")//匹配com.chenss.dao包下的任意接口和類的第一個參數爲String類型的方法

@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String))")//匹配com.chenss.dao包下的任意接口和類的只有一個參數,且參數爲String類型的方法

@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String))")//匹配com.chenss.dao包下的任意接口和類的只有一個參數,且參數爲String類型的方法

@Pointcut("execution(public * *(..))")//匹配任意的public方法

@Pointcut("execution(* te*(..))")//匹配任意的以te開頭的方法

@Pointcut("execution(* com.chenss.dao.IndexDao.*(..))")//匹配com.chenss.dao.IndexDao接口中任意的方法

@Pointcut("execution(* com.chenss.dao..*.*(..))")//匹配com.chenss.dao包及其子包中任意的方法

關於這個表達式的詳細寫法,可以腦補也可以參考官網很容易的,可以作爲一個看spring官網文檔的入門,打破你害怕看官方文檔的心理,其實你會發覺官方文檔也是很容易的

https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-pointcuts-examples
由於Spring切面粒度最小是達到方法級別,而execution表達式可以用於明確指定方法返回類型,類名,方法名和參數名等與方法相關的信息,並且在Spring中,大部分需要使用AOP的業務場景也只需要達到方法級別即可,因而execution表達式的使用是最爲廣泛的
  1. within
™表達式的最小粒度爲類
// ------------
// within與execution相比,粒度更大,僅能實現到包和接口、類級別。而execution可以精確到方法的返回值,參數個數、修飾符、參數類型等

@Pointcut("within(com.chenss.dao.*)")//匹配com.chenss.dao包中的任意方法
@Pointcut("within(com.chenss.dao..*)")//匹配com.chenss.dao包及其子包中的任意方法
  1. args
 args表達式的作用是匹配指定參數類型和指定參數數量的方法,與包名和類名無關

/**

 \* args同execution不同的地方在於:

 \* args匹配的是運行時傳遞給方法的參數類型

 \* execution(* *(java.io.Serializable))匹配的是方法在聲明時指定的方法參數類型。

 */

@Pointcut("args(java.io.Serializable)")//匹配運行時傳遞的參數類型爲指定類型的、且參數個數和順序匹配

@Pointcut("@args(com.chenss.anno.Chenss)")//接受一個參數,並且傳遞的參數的運行時類型具有@Classified
  1. this JDK代理時,指向接口和代理類proxy,cglib代理時 指向接口和子類(不使用proxy)

  2. target 指向接口和子類

/**

 \* 此處需要注意的是,如果配置設置proxyTargetClass=false,或默認爲false,則是用JDK代理,否則使用的是CGLIB代理

 \* JDK代理的實現方式是基於接口實現,代理類繼承Proxy,實現接口。

 \* 而CGLIB繼承被代理的類來實現。

 \* 所以使用target會保證目標不變,關聯對象不會受到這個設置的影響。

 \* 但是使用this對象時,會根據該選項的設置,判斷是否能找到對象。

 */

@Pointcut("target(com.chenss.dao.IndexDaoImpl)")//目標對象,也就是被代理的對象。限制目標對象爲com.chenss.dao.IndexDaoImpl類

@Pointcut("this(com.chenss.dao.IndexDaoImpl)")//當前對象,也就是代理對象,代理對象時通過代理目標對象的方式獲取新的對象,與原值並非一個

@Pointcut("@target(com.chenss.anno.Chenss)")//具有@Chenss的目標對象中的任意方法

@Pointcut("@within(com.chenss.anno.Chenss)")//等同於@targ
這個比較難.......

proxy模式裏面有兩個重要的術語

proxy Class

target Class

CGLIB和JDK有區別    JDK是基於接口   cglib是基於繼承所有this可以在cglib作用
  1. @annotation
這個很簡單........

作用方法級別

上述所有表達式都有@ 比如@Target(裏面是一個註解類xx,表示所有加了xx註解的類,和包名無關)

注意:上述所有的表達式可以混合使用,|| && !

@Pointcut("@annotation(com.chenss.anno.Chenss)")//匹配帶有com.chenss.anno.Chenss註解的方法
  1. bean
@Pointcut("bean(dao1)")//名稱爲dao1的bean上的任意方法

@Pointcut("bean(dao*)")

Spring AOP XML實現方式的注意事項:

  1. 在aop:config中定義切面邏輯,允許重複出現,重複多次,以最後出現的邏輯爲準,但是次數以出現的次數爲準

  2. aop:aspect ID重複不影響正常運行,依然能夠有正確結果

  3. aop:pointcut ID重複會出現覆蓋,以最後出現的爲準。不同aop:aspect內出現的pointcut配置,可以相互引用

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

​       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

​       xmlns:aop="http://www.springframework.org/schema/aop"

​       xmlns:context="http://www.springframework.org/schema/context"

​       xsi:schemaLocation="http://www.springframework.org/schema/beans

​                           http://www.springframework.org/schema/beans/spring-beans.xsd

​                           http://www.springframework.org/schema/aop

​                           http://www.springframework.org/schema/aop/spring-aop.xsd

​                           http://www.springframework.org/schema/context

​                           http://www.springframework.org/schema/context/spring-context.xsd">

​    <!-- 定義開始進行註解掃描 -->

​    <context:component-scan base-package="com.chenss"></context:component-scan>

​    <!-- 定義AspectJ對象使用的邏輯類,類中提供切面之後執行的邏輯方法 -->

​    <bean id="aspectAop" class="com.chenss.aspectj.Aspect"></bean>

​    <bean id="aspectAop2" class="com.chenss.aspectj.Aspect2"></bean>

​    <bean id="indexDao" class="com.chenss.entity.IndexDao"></bean>

​    <!--在Config中定義切面邏輯,允許重複出現,重複多次,以最後出現的邏輯爲準,但是次數以出現的次數爲準-->

​    <aop:config>

​        <!-- aop:aspect ID重複不影響正常運行,依然能夠有正確結果 -->

​        <!-- aop:pointcut ID重複會出現覆蓋,以最後出現的爲準。不同aop:aspect內出現的pointcut配置,可以相互引用 -->

​        <aop:aspect id="aspect" ref="aspectAop">

​            <aop:pointcut id="aspectCut" expression="execution(* com.chenss.entity.*.*())"/>

​            <aop:before method="before" pointcut-ref="aspectCut"></aop:before>

​      fffffff

​            <aop:pointcut id="aspectNameCut" expression="execution(* com.chenss.entity.*.*(java.lang.String, ..))"/>

​            <aop:before method="before2" pointcut-ref="aspectNameCut"></aop:before>

​        </aop:aspect>

​    </aop:config>

</beans>

spring AOP的源碼分析

cglib

img

img

cglib封裝了ASM這個開源框架,對字節碼操作,完成對代理類的創建

主要通過集成目標對象,然後完成重寫,再操作字節碼

具體看參考ASM的語法

JDK

在Proxy這個類當中首先實例化一個對象ProxyClassFactory,然後在get方法中調用了apply方法,完成對代理類的創建

img

img

其中最重要的兩個方法

generateProxyClass通過反射收集字段和屬性然後生成字節

defineClass0 jvm內部完成對上述字節的load

img

總結:cglib是通過繼承來操作子類的字節碼生成代理類,JDK是通過接口,然後利用java反射完成對類的動態創建,
嚴格意義上來說cglib的效率高於JDK的反射,但是這種效率取決於代碼功力,其實可以忽略不計,畢竟JDK是JVM的親兒子........

spring5新特性

1 使用 lambda表達式定義bean

2 日誌 spring4的日誌是用jcl,原生的JCL,底層通過循環去加載具·體的日誌實現技術,所以有先後順序,spring5利用的是spring-jcl,其實就是spring自己改了JCL的代碼具體參考視頻當中講的兩者的區別

新特性還有其他,但是這兩個比較重要,由於時間問題,其他的特性可以去網上找到相應資料,但是這兩個應付面試絕對可以了,其他的特性噱頭居多,實用性可能不是很大.

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