Spring AOP從入門到放棄之概念以及Spring Boot AOP demo
本文小福利 點我獲取阿裏雲優惠券
AOP核心概念
1、橫切關注點
對哪些方法進行攔截,攔截後怎麼處理,這些關注點稱之為橫切關注點
2、切麵(aspect)-》(通知+切點)
類是對物體特征的抽象,切麵就是對橫切關注點的抽象。
通知+切點
意思就是所有要被應用到增強(advice)代碼的地方。(包括方法的方位信息)
3、連接點(joinpoint)-》(被攔截的方法)
被攔截到的點,因為Spring隻支持方法類型的連接點,所以在Spring中連接點指的就是被攔截的方法,實際上連接點還可以是字段或者構造器
4、切入點(pointcut)-》(描述攔截那些方法的部分)
對連接點進行攔截的定義
5、通知(advice)-》(攔截後執行自己業務邏輯的那些部分)
所謂通知指的就是指攔截到連接點之後要執行的代碼,通知分為前置、後置、異常、最終、環繞通知五類
這玩意也叫 增強
在邏輯層次上包括了我們抽取的公共邏輯和方位信息。因為Spring隻能方法級別的應用AOP,也就是我們常見的before,after,after-returning,after-throwing,around五種,意思就是在方法調用前後,異常時候執行我這段公共邏輯唄。
6、目標對象
代理的目標對象
7、織入(weave)
將切麵應用到目標對象並導致代理對象創建的過程。
比如根據Advice中的方位信息在指定切點的方法前後,執行增強。這個過程Spring 替我們做好了。利用的是CGLIB動態代理技術。
8、引入(introduction)
在不修改代碼的前提下,引入可以在運行期為類動態地添加一些方法或字段
圖解
通知(Advice)類型
切麵一共有五種通知
Before 某方法調用之前發出通知。
前置通知(Before advice) :在某連接點(JoinPoint)之前執行的通知, 但這個通知不能阻止連接點前的執行。在方法調用之前發出通
@Before("execution(* com.slife.java8.aspect.AspectTest.test())")
public void beforeTest() {
System.out.println("執行 方法 之前 調用----");
}
After 某方法完成之後發出通知
後通知(After advice) :當某連接點退出的時候執行的通知(不論是正常 返回還是異常退出)。
不考慮方法運行的結果 。在方法調用之後發出通
@After("execution(* com.slife.java8.aspect.AspectTest.test())")
public void afterTest() {
System.out.println();
System.out.println("執行 方法 之後 調用----");
}
After-returning 將通知放置在被通知的方法成功執行之後。
方法正常返回後,調用通知。在方法調用後,正常退出發出通
@AfterReturning("execution(* com.slife.java8.aspect.AspectTest.test())")
public void afterReturningTest() {
System.out.println();
System.out.println("執行 方法 AfterReturning 調用----");
}
After-throwing 將通知放置在被通知的方法拋出異常之後。
拋出異常後通知(After throwing advice) : 在方法拋出異常退出時執行 的通知。在方法調用時,異常退出發出通
@AfterThrowing("execution(* com.slife.java8.aspect.AspectTest.test())")
public void afterThrowingTest() {
System.out.println();
System.out.println("執行 方法 AfterThrowing 調用----");
}
Around 通知包裹在被通知的方法的周圍知。
環繞通知(Around advice) :包圍一個連接點的通知,類似Web中Servlet 規範中的Filter的doFilter方法。可以在方法的調用前後完成自定義的行為,也可以選擇不執行。在方法調用之前和之後發出通
@Around("execution(* com.slife.java8.aspect.AspectTest.test())")
public void aroundTest() {
System.out.println();
System.out.println("執行 方法 前後 調用----");
}
執行結果
2017-10-27 19:51:51.605 DEBUG 15592 --- [io-8081-exec-10] o.s.web.servlet.DispatcherServlet : Last-Modified value for [/aspecttest] is: -1
執行 方法 之前 調用----
JoinpointTest++++執行我正常流水線的業務邏輯
執行 方法 之後 調用----
執行 方法 AfterReturning 調用----
2017-10-27 19:51:51.622 DEBUG 15592 --- [io-8081-exec-10] o.s.web.servlet.DispatcherServlet : Null ModelAndView returned to DispatcherServlet with name 'dispatcherServlet': assuming HandlerAdapter completed request handling
2017-10-27 19:51:51.622 DEBUG 15592 --- [io-8081-exec-10] o.s.web.servlet.DispatcherServlet : Successfully completed request
切入點表達式
切入點指示符用來指示切入點表達式目的,在Spring AOP中目前隻有執行方法這一個連接點,Spring AOP支持的AspectJ切入點指示符如下:
args()
定製join-point去匹配那些參數為指定類型的方法的執行動作。
@args()
定製join-point去匹配那些參數被指定類型注解的方法的執行動作
execution()
開始匹配在其內部編寫的定製
this()
定製join-pont去匹配由AOP代理的Bean引用的指定類型的類。
target()
定製join-point去匹配特定的對象,這些對象一定是指定類型的類。
@target()
定製join-point去匹配特定的對象,這些對象要具有的指定類型的注解。
within()
定製join-point在必須哪一個包中。
@within()
定製join-point在必須由指定注解標注的類中。
@annotation
定製連接點具有指定的注解。
隻有execution用來執行匹配,其他標誌符都隻是為了限製/定製他們所要匹配的連接點的位置。
命名及匿名切入點
類型匹配語法
*
:匹配任何數量字符。
..
:匹配任何數量字符的重複,如在類型模式中匹配任何數量子包;而在方法參數模式中匹配任何數量參數。
+
:匹配指定類型的子類型;僅能作為後綴放在類型模式後邊。
例子
java.lang.String 匹配String類型;
java.*.String 匹配java包下的任何“一級子包”下的String類型;
如匹配java.lang.String,但不匹配java.lang.ss.String
java..* 匹配java包及任何子包下的任何類型;
如匹配java.lang.String、java.lang.annotation.Annotation
java.lang.*ing 匹配任何java.lang包下的以ing結尾的類型;
java.lang.Number+ 匹配java.lang包下的任何Number的自類型;
如匹配java.lang.Integer,也匹配java.math.BigInteger
詳細語法
注解? 修飾符? 返回值類型 類型聲明?方法名(參數列表) 異常列表?
注解:可選,方法上持有的注解,如@Deprecated;
修飾符:可選,如public、protected;
返回值類型:必填,可以是任何類型模式;“*”表示所有類型;
類型聲明:可選,可以是任何類型模式;
方法名:必填,可以使用“*”進行模式匹配;
參數列表:“()”表示方法沒有任何參數;“(..)”表示匹配接受任意個參數的方法,“(..,java.lang.String)”表示匹配接受java.lang.String類型的參數結束,且其前邊可以接受有任意個參數的方法;“(java.lang.String,..)” 表示匹配接受java.lang.String類型的參數開始,且其後邊可以接受任意個參數的方法;“(*,java.lang.String)” 表示匹配接受java.lang.String類型的參數結束,且其前邊接受有一個任意類型參數的方法;
異常列表:可選,以“throws 異常全限定名列表”聲明,異常全限定名列表如有多個以“,”分割,如throws java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException。
匹配Bean名稱:可以使用Bean的id或name進行匹配,並且可使用通配符“*”;
組合切入點表達式
AspectJ使用 且(&&)、或(||)、非(!)來組合切入點表達式。在Schema風格下,由於在XML中使用“&&”需要使用轉義字符“&&”來代替之,所以很不方便,因此Spring ASP 提供了and、or、not來代替&&、||、!。
通知參數
使用JoinPoint獲取:Spring AOP提供使用org.aspectj.lang.JoinPoint類型獲取連接點數據,任何通知方法的第一個參數都可以是JoinPoint(環繞通知是ProceedingJoinPoint,JoinPoint子類),當然第一個參數位置也可以是JoinPoint.StaticPart類型,這個隻返回連接點的靜態部分。
運用場景
AOP 、IOC 做為Spring 的支柱,使用場景非常廣泛。
1、日誌記錄
2、權限控製
3、事務
4、多數據源讀寫切換
原理
動態代理
Spring中AOP代理由Spring的IOC容器負責生成、管理,其依賴關係也由IOC容器負責管理。因此,AOP代理可以直接使用容器中的其它bean實例作為目標,這種關係可由IOC容器的依賴注入提供。Spring創建代理的規則為:
1、默認使用Java動態代理來創建AOP代理,這樣就可以為任何接口實例創建代理了
2、當需要代理的類不是代理接口的時候,Spring會切換為使用CGLIB代理,也可強製使用CGLIB
spring boot 項目中定義使用自己的aop
1、引入jar
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2、編寫切麵
@Aspect // FOR AOP
@Order(-99) // 控製多個Aspect的執行順序,越小越先執行
@Component
public class AdviceTest {
@Pointcut(value="execution(* com.slife.java8.aspect.AspectTest.test())")
public void poincut(){
}
@Before("poincut()")
public void beforeTest() {
System.out.println("執行 方法 之前 調用----");
}
}
這樣就完成了在spring boot 項目中定義使用自己的aop
注意代理模式
CGLIB動態代理技術
有時候你會發現 你的配置和我一樣,但是aop沒有生效,這很有可能是SpringMVC的配置的代理模式不對。
問題描述
方法裏的xxxService對象如果使用autowared注入,無法啟動aspect,
但是
xxxService = ctx.getBean("xxxxx")獲取,是可以啟用aspect的
原因
這個時候 xxxService 並不是注入進來的,即使有 @Autowired 注解,這時的注解沒有任何作用。
隻有 Spring 生成的對象才有 AOP 功能,因為 Spring 生成的代理對象才有 AOP 功能。
解決方法
配置spring.aop.proxy-target-class=true
文章代碼
/**
* Created by chen on 2017/10/27.
* <p>
* Email 122741482@qq.com
* <p>
* Describe:
*/
@Service
public class JoinpointTest {
public void JoinpointTest(){
System.out.println("JoinpointTest*");
}
public void test(){
System.out.println("JoinpointTest++++執行我正常流水線的業務邏輯");
}
}
@Aspect // FOR AOP
@Order(-99) // 控製多個Aspect的執行順序,越小越先執行
@Component
public class AdviceTest {
@Pointcut(value="execution(* com.slife.java8.aspect.AspectTest.test())")
public void poincut(){
}
@Before("poincut()")
public void beforeTest() {
System.out.println("執行 方法 之前 調用----");
}
@After("execution(* com.slife.java8.aspect.AspectTest.test())")
public void afterTest() {
System.out.println();
System.out.println("執行 方法 之後 調用----");
}
@Around("execution(* com.slife.java8.aspect.AspectTest.test())")
public void aroundTest() {
System.out.println();
System.out.println("執行 方法 前後 調用----");
}
@AfterReturning("execution(* com.slife.java8.aspect.AspectTest.test())")
public void afterReturningTest() {
System.out.println();
System.out.println("執行 方法 AfterReturning 調用----");
}
@AfterThrowing("execution(* com.slife.java8.aspect.AspectTest.test())")
public void afterThrowingTest() {
System.out.println();
System.out.println("執行 方法 AfterThrowing 調用----");
}
@Before("execution( com.slife.java8..test*(..))")
public void aspecttest1() {
System.out.println();
System.out.println("執行 方法aspecttest1 Before 調用----");
}
}
點擊獲取阿裏雲優惠券
我的官網
[圖片上傳失敗...(image-8a5f4a-1509673150463)]
我的官網https://guan2ye.com
我的CSDN地址https://blog.csdn.net/chenjianandiyi
我的簡書地址https://www.jianshu.com/u/9b5d1921ce34
我的githubhttps://github.com/javanan
我的碼雲地址https://gitee.com/jamen/
阿裏雲優惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld
最後更新:2017-11-03 10:03:37