原文:https://www.cnblogs.com/hellxz/p/9629012.html
注意:在我們寫ssm或者spring框架等等項目時,一旦這個mapper的相關處理不能滿足現實的需求(比如要增強CRUD的時候,在本身對象實體不能滿足需求,就要重新構建出新的組合實體來滿足需求),就需要增加一個service層來控制CRUD邏輯,這個時候就需要在spring的xml文件中添加AOP的切點和織入。
圖片中聲明切面中的id就是織入的pointcut-ref屬性的值,織入的屬性值就是AOP配置事務的id,如下圖:
在此之前要進行如下操作:
Spring AOP基本概念
- 是一種動態編譯期增強性AOP的實現
- 與IOC進行整合,不是全面的切面框架
- 與動態代理相輔相成
- 有兩種實現:基於jdk動態代理、cglib
Spring AOP與AspectJ區別
- Spring的AOP是基於動態代理的,動態增強目標對象,而AspectJ是靜態編譯時增強,需要使用自己的編譯器來編譯,還需要織入器
- 使用AspectJ編寫的java代碼無法直接使用javac編譯,必須使用AspectJ增強的ajc增強編譯器才可以通過編譯,寫法不符合原生Java的語法;而Spring AOP是符合Java語法的,也不需要指定編譯器去編譯,一切都由Spring 處理。
JDK動態代理與Cglib的區別
- jdk的動態代理需要實現接口 InvocationHandler
- cglib無需實現接口,使用字節碼技術去修改class文件使繼承
- spring默認使用jdk動態代理,如果沒有實現接口會使用cglib
使用步驟
- 定義業務組件
- 定義切點(重點)
- 定義增強處理方法(切面方法)
依賴
jar包依賴,除此以外還有spring依賴
- aspectjweaver.jar
- aspectjrt.jar
- aspectj.jar
- aopalliance.jar
maven依賴
<dependencies> <!-- 有此依賴會遠程下載其它相關依賴 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.2.9.RELEASE</version> </dependency> <!-- aspectJ AOP 織入器 --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency> </dependencies>
注解方式開發
- 掃描Aspect增強的類
<context:component-scan base-package=""> <context:include-filter type="annotation" expression="org.aspectj.lang.annotation.Aspect"/> </context:component-scan>
- 開啟@AspectJ支持
<aop:aspectj-autoproxy/>
- 使用@AspectJ注解來標記一個切面類(spring不會將切面注冊為Bean也不會增強,但是需要掃描)
- 使用其它注解進行開發(如下)
常用注解的使用
- @Before:在切點方法前執行
- 在增強的方法上
@Before("execution(* 包名.*.*(..))")
- 上述表達式可使用pointcut或切入表達式,效果一致,之后不再贅述
- 切點方法沒有形參與返回值
- 在增強的方法上
示例代碼
@Aspect public class AuthAspect { //定義切點 @Pointcut("execution(* com.cnblogs.hellxz.service.*.*(..))") public void pointCut() {} //前置處理 @Before("pointCut()") public void auth() { System.out.println("模擬權限檢查……"); } }
- @After:在切點方法后執行
- 用法同@Before
- @Around:在切點方法外環繞執行
- 在增強的方法上
@Around("execution(* 包名.*(..))")
或使用切點@Around("pointcut()")
- 接收參數類型為
ProceedingJoinPoint
,必須有這個參數在切面方法的入參第一位 - 返回值為Object
- 需要執行ProceedingJoinPoint對象的proceed方法,在這個方法前與后面做環繞處理,可以決定何時執行與完全阻止方法的執行
- 返回proceed方法的返回值
- @Around相當於@Before和@AfterReturning功能的總和
- 可以改變方法參數,在proceed方法執行的時候可以傳入Object[]對象作為參數,作為目標方法的實參使用。
- 如果傳入Object[]參數與方法入參數量不同或類型不同,會拋出異常
- 通過改變proceed()的返回值來修改目標方法的返回值
- 在增強的方法上
示例代碼
@Aspect public class TxAspect { //環繞處理 @Around("execution(* com.cnblogs.hellxz.service.*.*(..))") Object auth(ProceedingJoinPoint point) { Object object = null; try { System.out.println("事務開啟……"); //放行 object = point.proceed(); System.out.println("事務關閉……"); } catch (Throwable e) { e.printStackTrace(); } return object; } }
- @AfterRetruning: 在方法返回之前,獲取返回值並進行記錄操作
- 和上邊的方法不同的地方是該注解除了切點,還有一個返回值的對象名
- 不同的兩個注解參數:returning與pointcut,其中pointcut參數可以為切面表達式,也可為切點
- returning定義的參數名作為切面方法的入參名,類型可以指定。如果切面方法入參類型指定Object則無限制,如果為其它類型,則當且僅當目標方法返回相同類型時才會進入切面方法,否則不會
- 還有一個默認的value參數,如果指定了pointcut則會覆蓋value的值
- 與@After類似,但@AfterReturning只有方法成功完成才會被織入,而@After不管結果如何都會被織入
雖然可以拿到返回值,但無法改變返回值
示例代碼
@Aspect public class AfterReturningAspect { @AfterReturning(returning="rvt", pointcut = "execution(* com.cnblogs.hellxz.service.*.*(..))") //聲明rvt時指定的類型會限定目標方法的返回值類型,必須返回指定類型或者沒有返回值 //rvt類型為Object則是不對返回值做限制 public void log(Object rvt) { System.out.println("獲取目標返回值:"+ rvt); System.out.println("假裝在記錄日志……"); } /** * 這個方法可以看出如果目標方法的返回值類型與切面入參的類型相同才會執行此切面方法 * @param itr */ @AfterReturning(returning="itr", pointcut="execution(* com.cnblogs.hellxz.service.*.*(..))") public void test(Integer itr) { System.out.println("故意搗亂……:"+ itr); } }
- @AfterThrowing: 在異常拋出前進行處理,比如記錄錯誤日志
- 與
@AfterReturning
類似,同樣有一個切點和一個定義參數名的參數——throwing - 同樣可以通過切面方法的入參進行限制切面方法的執行,e.g. 只打印IOException類型的異常, 完全不限制可以使用Throwable類型
- pointcut使用同
@AfterReturning
- 還有一個默認的value參數,如果指定了pointcut則會覆蓋value的值
- 如果目標方法中的異常被try catch塊捕獲,此時異常完全被catch塊處理,如果沒有另外拋出異常,那么還是會正常運行,不會進入AfterThrowing切面方法
- 與
示例代碼
@Aspect public class AfterThrowingAspect { @Pointcut("execution(* com.cnblogs.hellxz.test.*.*(..))") public void pointcut() {} /** * 如果拋出異常在切面中的幾個異常類型都滿足,那么這幾個切面方法都會執行 */ @AfterThrowing(throwing="ex1", pointcut="pointcut()") //無論異常還是錯誤都會記錄 //不捕捉錯誤可以使用Exception public void throwing(Throwable ex1) { System.out.println("出現異常:"+ex1); } @AfterThrowing(throwing="ex", pointcut="pointcut()") //只管IOException的拋出 public void throwing2(IOException ex) { System.out.println("出現IO異常: "+ex); } }
pointcut定義的切點方法在@Before/@After/@Around需要寫在雙引號中,e.g. @Before("pointCut()")
JoinPoint的概念與方法說明
概念
- 顧名思義,連接點,織入增強處理的連接點
- 程序運行時的目標方法的信息都會封裝到這個連接點對象中
- 此連接點只讀
方法說明
Object[] getArgs()
:返回執行目標方法時的參數Signature getSignature()
:返回被增強方法的相關信息,e.g 方法名 etcObject getTarget()
:返回被織入增強處理的目標對象Object getThis()
:返回AOP框架目標對象生成的代理對象
使用
- 在@Before/@After/@AfterReturning/@AfterThrowing所修飾的切面方法的參數列表中加入JoinPoint對象,可以使用這個對象獲得整個增強處理中的所有細節
- 此方法不適用於@Around, 其可用ProceedingJoinPoint作為連接點
ProceedingJoinPoint的概念與方法說明
概念
- 是JoinPoint的子類
- 與JoinPoint概念基本相同,區別在於是可修改的
- 使用@Around時,第一個入參必須為ProceedingJoinPoint類型
- 在@Around方法內時需要執行proceed()或proceed(Object[] args)方法使方法繼續,否則會一直處於阻滯狀態
方法說明
ProceedingJoinPoint是JoinPoint的子類,包含其所有方法外,還有兩個公有方法
Object proceed()
:執行此方法才會執行目標方法-
Object proceed(Object[] args)
:執行此方法才會執行目標方法,而且會使用Object數組參數去代替實參,如果傳入Object[]參數與方法入參數量不同或類型不同,會拋出異常通過修改proceed方法的返回值來修改目標方法的返回值
編入的優先級
優先級最高的會最先被織入,在退出連接點的時候,具有最高的優先級的最后被織入
當不同切面中兩個增強處理切入同一連接點的時候,Spring AOP 會使用隨機織入的方式
如果想要指定優先級,那么有兩種方案:
- 讓切面類實現
org.springframework.core.Ordered
接口,實現getOrder方法,返回要指定的優先級 - 切面類使用
@Order
修飾,指定一個優先級的值,值越小,優先級越高
訪問目標方法的形參
除了使用JoinPoint或ProceedingJoinPoint來獲取目標方法的相關信息外(包括形參),如果只是簡單訪問形參,那么還有一種方法可以實現
- 在pointcut的execution表達式之后加入
&& args(arg0,arg1)
這種方式
@Aspect public class AccessInputArgs { @Before("execution(* com.cnblogs.hellxz.test.*.*(..)) && args(arg0, arg1)") public void access(String arg0, String arg1){ System.out.println("接收到的參數為arg0="+arg0+",arg1="+arg1); } }
注意:通過這種方式會只匹配到方法只有指定形參數量的方法,並且,在切面方法中指定的類型會限制目標方法,不符合條件的不會進行織入增強
定義切入點
通過定義切入點,我們可以復用切點,減少重復定義切點表達式等
切入點定義包含兩個部分:
- 切入點表達式
- 包含名字和任意參數的方法簽名
使用@Pointcut注解進行標記一個無參無返回值的方法,加上切點表達式
@Pointcut("execution(* com.cnblogs.hellxz.test.*.*(..))") public void pointcut(){}
切入點指示符
Spring AOP 支持10種切點指示符:execution、within、this、target、args、@target、@args、@within、@annotation、bean下面做下簡記(沒有寫@Pointcut(),請注意):
-
execution: 用來匹配執行方法的連接點的指示符。
用法相對復雜,格式如下:execution(權限訪問符 返回值類型 方法所屬的類名包路徑.方法名(形參類型) 異常類型)
e.g. execution(public String com.cnblogs.hellxz.test.Test.access(String,String))
權限修飾符和異常類型可省略,返回類型支持通配符,類名、方法名支持*通配,方法形參支持..通配 -
within: 用來限定連接點屬於某個確定類型的類。
within(com.cnblogs.hellxz.test.Test)
within(com.cnblogs.hellxz.test.) //包下類
within(com.cnblogs.hellxz.test..) //包下及子包下 - this和target: this用於沒有實現接口的Cglib代理類型,target用於實現了接口的JDK代理目標類型
舉例:this(com.cnblogs.hellxz.test.Foo) //Foo沒有實現接口,使用Cglib代理,用this
實現了個接口public class Foo implements Bar{...}
target(com.cnblogs.hellxz.test.Test) //Foo實現了接口的情況 - args: 對連接點的參數類型進行限制,要求參數類型是指定類型的實例。
args(Long) - @target: 用於匹配類頭有指定注解的連接點
@target(org.springframework.stereotype.Repository) - @args: 用來匹配連接點的參數的,@args指出連接點在運行時傳過來的參數的類必須要有指定的注解
java @Pointcut("@args(org.springframework.web.bind.annotation.RequestBody)") public void methodsAcceptingEntities() {}
- @within: 指定匹配必須包括某個注解的的類里的所有連接點
@within(org.springframework.stereotype.Repository) - @annotation: 匹配那些有指定注解的連接點
@annotation(org.springframework.stereotype.Repository) -
bean: 用於匹配指定Bean實例內的連接點,傳入bean的id或name,支持使用*通配符
切點表達式組合
使用&&、||、!、三種運算符來組合切點表達式,表示與或非的關系
execution(* com.cnblogs.hellxz.test.*.*(..)) && args(arg0, arg1)