AOP和spring AOP學習記錄
阿新 • • 發佈:2020-03-09
### AOP基本概念的理解
面向切面AOP主要是在編譯期或執行時,對程式進行織入,實現代理,
對原始碼毫無侵入性,不破壞主要業務邏輯,減少程式的耦合度。
- 主要應用範圍:
日誌記錄,效能統計,安全控制,事務處理,異常處理等等
#### 名詞性概念
- **切面(Aspect)**
通常是一個類,在裡面可以定義切入點和通知,即`切面=切入點+通知`。
- **連線點(Joint Point)**
被攔截到的點,因為 Spring 只支援方法型別的連線點,所以在 Spring 中連線點指的就是被攔截的到的方法,實際上連線點還可以是欄位或者構造器。
- **切入點(Pointcut)**
對連線點進行攔截的定義。
- **通知(Advice)**
攔截到連線點之後所要執行的程式碼,通知分為前置、後置、異常、最終、環繞通知五類。
- **AOP 代理**
AOP 框架建立的物件,代理就是目標物件的加強。Spring 中的 AOP 代理可以使 JDK 動態代理,也可以是 CGLIB 代理,前者基於介面,後者基於子類。
- **織入**(Weaving)
把切面加入到物件,並創建出代理物件的過程。
#### 動態代理
在執行期間生成物件進行代理,
spring AOP就是動態代理。
#### 靜態代理
自己編寫代理物件,在編譯器織入,
AspectJ就是在編譯期間進行織入,從而減少對執行時效率的影響。
# SpringAOP
根據物件是否是實現類選擇代理的方法
- 如果要代理的物件實現了介面,spring AOP會根據介面,使用JDK Proxy建立代理
- 如果沒有實現介面,則通過CGLIB代理
當然也可以指定都使用CGLIB進行切入,而JDK方法不適用於沒有介面實現的目標類
#### 對於JDK proxy
Jdk proxy會實現目標類的介面,代理邏輯就在新的實現類中
只能對實現類進行代理,效能也優於CGLIB,所以平時都是分為"介面-實現iml"來設計。
#### 對於CGLIB
cglib會建立一個代理目標類的子類,而代理邏輯就在這一子類中新增,
然後得到這一子類的物件,也就是代理增強後的物件,一切都是動態的,
具體實現有待學習。
## 切面類
使用@Aspect註解定義切面類
比如這樣
```java
@Component
@Aspect
@Oder(99)
public class WebLogAspect {}
```
另外對同一個方法有多個切面進行代理的時候,難免需要區分執行順序,
這時候可以使用@Order註解定義優先順序,數字越低,級別越高。
## 切面Advice
執行順序around -> before -> around -> after -> afterRuternning
#### @After
第一個引數都必須是JoinPoint型別
在目標方法完成後通知,
無論方法是以何種方式完成,異常也是如此
#### @After-returning
第一個引數都必須是JoinPoint型別
結束並安全返回時通知,異常不通知
#### @After-throwing
第一個引數都必須是JoinPoint型別
異常時通知
#### @Before
第一個引數都必須是JoinPoint型別
顧名思義
####@Around
最強大的通知
第一個形參必須是ProceedingJoinPoint型別,
```java
public interface ProceedingJoinPoint extends JoinPoint {
void set$AroundClosure(AroundClosure var1);
default void stack$AroundClosure(AroundClosure arc) {
throw new UnsupportedOperationException();
}
Object proceed() throws Throwable;
Object proceed(Object[] var1) throws Throwable;
}
```
可以看到它繼承自JoinPoint,多了proceed方法
註解的方法體內呼叫ProceedingJoinPoint引數的**proceed()**方法才會執行目標。
呼叫這個方法時,還可以傳入一個Object[]物件作為引數
可以通過這個切面**決定方法是否執行,改變傳入引數,改變返回值,檢查異常等**。
#### 引數方法
- getArgs(): Returns the method arguments.
- getThis(): Returns the proxy object.
- getTarget(): Returns the target object.
- getSignature(): Returns a description of the method that is being advised.
- toString(): Prints a useful description of the method being advised.
## 切點表示式
有關切點表示式,建議閱讀 [傳送門](https://www.jianshu.com/p/a0b9c53ac019)
可以定義一個切點,之後就不用一一指定,直接帶入value即可,例如:
```java
@Pointcut("execution(* com.hyg.app.controller..*.*(..))")
public void webLog(){}
@Before(value = "webLog()")
public void doBefore (JoinPoint jp){
String name = jp.getSignature().getName();
System.out.println(name+"開始執行");
}
```
### execution
表示式中最常用的方法是execution,粒度最小
對於`execution(* com.hyg.app.controller..*.*(..))`
- `execution `表示式的型別指定
- 第一個`* `代表 任意的**返回值型別**
- com.jiuxian aop所切的**包名**
- 包後面`.. `表示**當前包**及**其子包**,一個`.`是當前包,兩個`.`就包括所有子包
- 第二個`*` 表示類名,代表所有類
- `.*(..) `表示任何方法, 括號代表引數 .. 表示任意引數
後面那串東西,可以記為每一個`.`都是更深的一個粒度
來個精確點的示例:
`execution(* com.hyg.app.service.*Service.add*(String))`
表示`所有型別`的,`com.hyg.app.service`包下的,
所有以`Service`結尾的類,
以`add`開頭的,引數型別為`String`的方法
### 例子
在官方文件中有很多示例,如下
```
所有public方法
execution(public * *(..))
所有名字以set開頭的方法
execution(* set*(..))
所有AccountService中實現的介面的方法
execution(* com.xyz.service.AccountService.*(..))
所有service包下的方法
execution(* com.xyz.service.*.*(..))
所有在service包以及子包下的方法
execution(* com.xyz.service..*.*(..))
within(com.xyz.service.*) 所有service包下方法
within(com.xyz.service..*) 所有service包和子包下方法
this(com.xyz.service.AccountService) 匹配accountservice代理的物件
target(com.xyz.service.AccountService) 實現了AccountService介面的物件
擁有transactional註解的
@annotation(org.springframework.transaction.annotation.Transactional)
傳遞的引數是Serializable的
args(java.io.Serializable):
```
### 關於execution和within的區別
execution可以指定方法返回型別,類名,方法名和引數名等與方法相關的部件,
而within的最小粒度是類。
### 關於this和target的區別
this匹配的是代理類,即目標物件被代理後的代理物件
target則是匹配普通的目標物件
以下情況外,二者的效果都是一致的。
this(SomeObject)或target(SomeObject),這裡SomeObject實現了某個介面:對於這種情況,雖然表示式中指定的是一種具體的物件型別,但由於其實現了某個介面,因而Spring預設會使用Jdk代理為其生成代理物件,Jdk代理生成的代理物件與目標物件實現的是同一個介面,但代理物件與目標物件還是不同的物件,由於代理物件不是SomeObject型別的,因而此時是不符合this語義的,而由於目標物件就是SomeObject型別,因而target語義是符合的,此時this和target的效果就產生了區別;這裡如果強制Spring使用Cglib代理,因而生成的代理物件都是SomeObject子類的物件,其是SomeObject型別的,因而this和target的語義都符合,其效果就是一致的。
### args
用於指定引數型別,型別必須是全路徑的
### 官網解釋
- `execution`: For matching method execution join points. This is the primary pointcut designator to use when working with Spring AOP.
- `within`: Limits matching to join points within certain types (the execution of a method declared within a matching type when using Spring AOP).
- `this`: Limits matching to join points (the execution of methods when using Spring AOP) where the bean reference (Spring AOP proxy) is an instance of the given type.
- `target`: Limits matching to join points (the execution of methods when using Spring AOP) where the target object (application object being proxied) is an instance of the given type.
- `args`: Limits matching to join points (the execution of methods when using Spring AOP) where the arguments are instances of the given types.
- `@target`: Limits matching to join points (the execution of methods when using Spring AOP) where the class of the executing object has an annotation of the given type.
- `@args`: Limits matching to join points (the execution of methods when using Spring AOP) where the runtime type of the actual arguments passed have annotations of the given types.
- `@within`: Limits matching to join points within types that have the given annotation (the execution of methods declared in types with the given annotation when using Spring AOP).
- `@annotation`: Limits matching to join points where the subject of the join point (the method being executed in Spring AOP) has the given annotation.
[---官方文件---](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop)
我的部落格:[https://www.seyana.life/post/12](https://www.seyana.life/p