Spring Aop 詳解二
阿新 • • 發佈:2020-10-21
>這是Spring Aop的第二篇,案例程式碼很詳解。基本概念可以檢視前文。
## 切入點表示式
### execution
execution表示式是到方法級別,具體構成檢視上一篇文章中 [Spring Aop 詳解一](https://www.cnblogs.com/mxjhaima/p/13834298.html#%E5%88%87%E5%85%A5%E7%82%B9%E8%A1%A8%E8%BE%BE%E5%BC%8F)
#### 完全不限制(執行時報錯)
正常情況下,我們可以寫出這樣不會編譯報錯的表示式,但是執行效果就呵呵了。不知道是不是規避這種不限制的規則。也不知道是不是我寫錯了,有搞明白的師兄可以指點一下。
```
@Before("execution(* *..*(..))")
```
#### 限制到包的所有方法
```
@Before("execution(* demo.aop.service.*(..))") //demo.aop.service包
@Before("execution(* demo.aop.service..*(..))") //demo.aop.service包及子包
@Before("execution(* demo.aop.service..save*(..))")//demo.aop.service 包及子包中sava開頭的方法
```
#### 引數限制
```
@Before("execution(* demo.aop.service..*()) ")//demo.aop.service 包及子包中 不需要引數的方法 如save()方法
//demo.aop.service 包及子包中引數列表為 (int,String) 的方法,如save(int age, String name)方法
@Before("execution(* demo.aop.service..*(int,String)) ")
//限制方法引數時,如果引數是複雜型別引數,需要將這個型別寫完整:java.util.List
@Before("execution(* demo.aop.service..*(java.util.List)) ")
```
#### 且或非
```
//表示式可以用 '&', '||' 和 '!' 做合併
//包含 demo.aop.controller 下所有類的所有方法 且 不包含 demo.aop.controller.PassportController的所有方法
@Before("execution(* demo.aop.controller..*(..)) " +
" && !execution(* demo.aop.controller.PassportController.*(..))"
+ " || execution(* demo.aop.service..*(..))"
)
```
### within
**within表示確切到類的所有方法,表示式只表達到類級別**
```
//所有controller包裡所有的類的方法
@Before("within(demo.aop.controller.*)")
//所有controller包,及子包 裡所有的類的方法
@Before("within(demo.aop.controller..*)")
//within 確切到具體的類WithinTestController
@Before("within(demo.aop.controller.WithinTestController)")
```
### bean
```
//通過bean制定 spring容器中的名為beanTestController的bean
//spring中的bean名字會預設小寫首字母,或者顯示的命名 @RestController("beanTestController")
@Before("bean(beanTestController)")
```
## 引數引用 args
如果我們在通知中需要獲取到被切入方法(連線點)的引數,那麼我們可以通過args表示式來引用。
### 引用
```
//引數引用,講切入點的引數引入到通知裡面去
args這裡除了引用連線點中的引數之外,還有限制的作用,也就是它只會匹配擁有(String name, Integer age)引數的連線點
@Before("execution(* demo.aop.controller..*.*(..)) && args( name, age)")
public void cut2(String name, Integer age) {
log.info("引數引用,講切入點的引數引入到通知裡面去");
log.info(name);
log.info(age.toString());
}
```
### 限制
```
//限制引數列表的型別為 ( ),切入沒有引數的方法
@Before("execution(* demo.aop.controller..*.*(..)) && args( )")
public void cut() {
log.info("限制引數列表的型別為 ()");
}
//限制引數列表的型別為 ( String, Integer)
@Before("execution(* demo.aop.controller..*.*(..)) && args( String, Integer)")
public void cut1() {
log.info("限制引數列表的型別為 ( String, Integer)");
}
```
## 引入(Introduction)
上一篇中概念中提到了引入,具體的實現我們來看一個案例
- 切面類DeclareParentsAspect.java
```java
package demo.aop.aspect;
import demo.aop.introduction.CommonParent;
import demo.aop.introduction.CommonParentImpl;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.DeclareParents;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Slf4j
public class DeclareParentsAspect {
//demo.spring.aop.service..* 代表demo.spring.aop.service包和子包下的所有類
//這樣聲明瞭service包中的類都會引入一個CommonParent父介面,並用CommonParentImpl實現,形成一個代理物件commonParent
@DeclareParents(value="demo.aop.service..*", defaultImpl= CommonParentImpl.class)
private CommonParent parent;
@Before("execution (* demo.aop.service.UserService.*(..)) && this(commonParent)")
public void beforeUserService(CommonParent commonParent) {
log.info(commonParent.getClass().toString());
commonParent.doSomething();
}
}
```
- 具體引入的介面的實現CommonParent.java CommonParentImpl.java
```
package demo.aop.introduction;
public interface CommonParent {
public void doSomething();
}
package demo.aop.introduction;
public class CommonParentImpl implements CommonParent {
@Override
public void doSomething() {
log.info("doSomething");
}
}
```
- 測試介面 DeclareParentsTestController.java
```java
package demo.aop.controller;
import demo.aop.service.RoleService;
import demo.aop.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class DeclareParentsTestController {
@Autowired
UserService userService;
@Autowired
RoleService roleService;
@GetMapping("/declare")
public String test(){
userService.save();
return "ok";
}
}
```
訪問http://localhost:8080/declare 執行結果如下
```
2020-10-21 10:22:53.645 demo.aop.aspect.DeclareParentsAspect : class demo.aop.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$58f5d27e
2020-10-21 10:22:53.645 demo.aop.introduction.CommonParentImpl : 引入(Introduction)測試 doSomething
```
### this
表示式中的this在前文 [Spring Aop 詳解一](https://www.cnblogs.com/mxjhaima/p/13834298.html#%E5%88%87%E5%85%A5%E7%82%B9%E8%A1%A8%E8%BE%BE%E5%BC%8F)。這裡和args類似,但這裡是直接把service包中的代理類引入進來,我們輸出了 commonParent的型別是`demo.aop.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$58f5d27e`代理物件,並且這個代理物件通過代理得到了`doSomething()`方法。
### @DeclareParents
該註解傳入一個類似切入點表示式的表示式,讓所有的demo.aop.service中的介面的spring bean都成為了代理類了。
接下來我們證明所有的的service都已經成了代理類了。我們修改一下測試介面如下
```java
package demo.aop.controller;
import demo.aop.service.RoleService;
import demo.aop.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class DeclareParentsTestController {
@Autowired
UserService userService;
@Autowired
RoleService roleService;
@GetMapping("/declare")
public String test(){
userService.save();
return "ok";
}
@GetMapping("/declare2")
public String test2(){
log.info(roleService.getClass().toString());
CommonParent commonParent=(CommonParent)roleService;
commonParent.doSomething();
roleService.save();
return "ok";
}
}
```
訪問http://localhost:8080/declare2 執行結果如下
```
2020-10-21 11:04:04.976 d.a.c.DeclareParentsTestController : class demo.aop.service.impl.RoleServiceImpl$$EnhancerBySpringCGLIB$$e316d514
2020-10-21 11:04:04.977 demo.aop.introduction.CommonParentImpl : 引入(Introduction)測試 doSomething
```
解讀如下
- `execution (* demo.aop.service.UserService.*(..))`使得beforeUserService通知不會再roleService的save方法執行。
- `@DeclareParents(value="demo.aop.service..*", defaultImpl= CommonParentImpl.class)`會使得 roleService 成為一個引入了CommonParent介面實現的 **代理物件**
- 因為roleService是**代理物件(劃重點)**,並且是實現了`CommonParent`介面,所以能夠型別轉換再呼叫`doSomething()`方法
## 目標物件引用 target
接著在DeclareParentsAspect.java中新增如下方法
```
@Before("execution (* demo.aop.service..*.*(..)) && target(o)")
public void beforeUserService(Object o) {
log.info(o.getClass().toString());
}
```
訪問http://localhost:8080/declare2 執行結果如下
```
2020-10-21 11:20:45.329 demo.aop.aspect.DeclareParentsAspect : class demo.aop.service.impl.RoleServiceImpl
```
通過target表示式,我們可以引用得到`目標物件`,**目標物件就是被代理的物件,也就是未被切入,也沒被代理的物件(官方文件:這個物件永遠是一個被代理(proxied)物件)。**
## 連線點物件(JoinPoint)
在前文中我們使用了環繞通知,而環繞通知中用到了連線點`ProceedingJoinPoint`,ProceedingJoinPoint是JoinPoint的子類。其他的通知我們可以使用JoinPoint來引入。
- 切面 JoinPointAspect.java
```
package demo.aop.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect
@Component
public class JoinPointAspect {
//JoinPoint 介面提供了一系列有用的方法,
// 比如 getArgs()(返回方法引數)、
// getThis()(返回代理物件)、
// getTarget()(返回目標物件)、
// getSignature()(返回正在被通知的方法相關資訊)和 toString() (打印出正在被通知的方法的有用資訊)
@Before("execution (* demo.aop.controller.JoinPointTestController.before(..))")
public void jp3(JoinPoint point) {
System.out.println(Arrays.toString(point.getArgs()));
System.out.println(point.getThis().getClass());
System.out.println(point.getTarget().getClass());
}
}
```
- 測試介面 JoinPointTestController.java
```
package demo.aop.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class JoinPointTestController {
@GetMapping("/join/point/Before")
public String before(String name){
return "ok";
}
}
```
訪問http://localhost:8080/join/point/Before?name=xxx 後臺輸出結果為
```
[xxx]
class demo.aop.controller.JoinPointTestController$$EnhancerBySpringCGLIB$$6a0dc9a4
class demo.aop.controller.JoinPointTestController
```
## 下文預告
- 通知優先順序
- @ControllerAdvice 實現統一錯誤處理
> 完整程式碼 [https://gitee.com/haimama/java-study/tree/master/spring-aop-demo](https://gitee.com/haimama/java-study/tree/master/spring-ao