1. 程式人生 > >Spring Aop 詳解二

Spring Aop 詳解二

>這是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