SpringBoot中的Aop用法示例詳解
什么是Aop
主要介紹springboot中aop的使用,用過Spring框架的都知道,aop是spring框架的兩大核心功能之一,還有一個就是ioc,下面我們就springboot中如何引入aop來做一下探討
- 引入AOP依賴包后,一般來說并不需要去做其他配置,使用過Spring注解配置方式的人會問是否需要在程序主類中增加@EnableAspectJAutoProxy來啟用,實(shí)際并不需要。
- 因?yàn)樵贏OP的默認(rèn)配置屬性中,spring.aop.auto屬性默認(rèn)是開啟的,也就是說只要引入了AOP依賴后,默認(rèn)已經(jīng)增加了@EnableAspectJAutoProxy
- Springboot中有關(guān)AOP相關(guān)的自動配置包為:AopAutoConfiguration
因?yàn)樵贏OP的默認(rèn)配置屬性中,spring.aop.auto屬性默認(rèn)是開啟的,也就是說只要引入了AOP依賴后,默認(rèn)已經(jīng)增加了@EnableAspectJAutoProxy
Springboot中有關(guān)AOP相關(guān)的自動配置包為:AopAutoConfiguration
Aop概述
相關(guān)注解
- @Component :將當(dāng)前類注入到Spring容器內(nèi)
- @Aspect :表明是一個切面類
- @Before :前置通知,在方法執(zhí)行之前執(zhí)行
- @After :后置通知,在方法執(zhí)行之后執(zhí)行
- @AfterRuturning :返回通知,在方法返回結(jié)果之后執(zhí)行
- @AfterThrowing :異常通知,在方法拋出異常之后執(zhí)行
- @Around :環(huán)繞通知,圍繞著方法執(zhí)行
- @Pointcut :切入點(diǎn),PointCut(切入點(diǎn))表達(dá)式有很多種,其中execution用于使用切面的連接點(diǎn)。
上面所提到的五種通知方法中,除了環(huán)繞通知外,其他的四個通知注解中,加或者不加參數(shù)JoinPoint都可以,如果有用到JoinPoint的地方就加,用不到就可以不加。
JoinPoint:里包含了類名、被切面的方法名,參數(shù)等屬性。
環(huán)繞通知:參數(shù)必須為ProceedingJoinPoint,pjp.proceed相應(yīng)于執(zhí)行被切面的方法。
返回通知:可以加returning = “XXX”,XXX即為被切入方法的返回值,本例中是controller類中方法的返回值。
異常通知:可以加throwing = “XXX”,供讀取異常信息。
返回通知和異常通知只會執(zhí)行一個,如果產(chǎn)生異常,返回通知就不執(zhí)行,后置通知一定會執(zhí)行
== 環(huán)繞通知一般單獨(dú)使用,環(huán)繞通知可以替代上面的四種通知,后面單獨(dú)介紹。==
相關(guān)概念
- Joinpoint(連接點(diǎn)):所謂連接點(diǎn)是指那些被攔截到的點(diǎn),在 spring 中,這些點(diǎn)指的是方法,因?yàn)?spring 只支持方法類型的連接點(diǎn),通俗的說就是被增強(qiáng)類中的所有方法
- PointCut(切入點(diǎn)):所謂切入點(diǎn)是指我們要對哪些 Joinpoint 進(jìn)行攔截的定義,通俗的說就是被增強(qiáng)類中的被增強(qiáng)的方法,因?yàn)楸辉鰪?qiáng)類中并不是所有的方法都被代理了
- Advice(通知/增強(qiáng)):所謂通知是指攔截到 Joinpoint (被增強(qiáng)的方法)之后所要做的事情就是通知,通俗的說就是對被增強(qiáng)的方法進(jìn)行增強(qiáng)的代碼
- 通知的類型:前置通知,返回通知,異常通知,后置通知,環(huán)繞通知
- 前置通知:在被代理方法執(zhí)行之前執(zhí)行
- 返回通知:在被代理方法執(zhí)行之后執(zhí)行
- 異常通知:在被代理方法執(zhí)行出錯的時候執(zhí)行
- 后置通知:無論怎樣都會執(zhí)行
- Aspect(切面):是切入點(diǎn)和通知(引介)的結(jié)合,通俗的說就是建立切入點(diǎn)和通知方法在創(chuàng)建時的對應(yīng)關(guān)系
注意:返回通知和異常通知只能有一個會被執(zhí)行,因?yàn)榘l(fā)生異常執(zhí)行異常通知,然后就不會繼續(xù)向下執(zhí)行,自然后置通知也就不會被執(zhí)行,反之亦然。
實(shí)例1
使用aop來完成全局請求日志處理。
第一步:使用Spring Initializr快速創(chuàng)建一個springboot web項(xiàng)目,名稱為testaop
第二步:引入aop相關(guān)的依賴
<!--aop相關(guān)的依賴引入--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
第三步:創(chuàng)建個controller
@RestController public class HelloController { @RequestMapping("/helloAop") public Object hello(){ return "hello aop"; } @RequestMapping("/helloError") public Object helloError(){ return 1/0; } }
第四步:創(chuàng)建一個aspect切面類
@Aspect @Component public class MyAop { //切入點(diǎn):待增強(qiáng)的方法 @Pointcut("execution(public * com.aismall.testaop.controller.*.*(..))") //切入點(diǎn)簽名 public void log(){ System.out.println("pointCut簽名。。。"); } //前置通知 @Before("log()") public void deBefore(JoinPoint jp) throws Throwable { // 接收到請求,記錄請求內(nèi)容 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 記錄下請求內(nèi)容 System.out.println("URL : " + request.getRequestURL().toString()); System.out.println("HTTP_METHOD : " + request.getMethod()); System.out.println("CLASS_METHOD : " + jp); System.out.println("ARGS : " + Arrays.toString(jp.getArgs())); } //返回通知 @AfterReturning(returning = "ret", pointcut = "log()") public void doAfterReturning(Object ret) throws Throwable { // 處理完請求,返回內(nèi)容 System.out.println("返回通知:方法的返回值 : " + ret); } //異常通知 @AfterThrowing(throwing = "ex", pointcut = "log()") public void throwss(JoinPoint jp,Exception ex){ System.out.println("異常通知:方法異常時執(zhí)行....."); System.out.println("產(chǎn)生異常的方法:"+jp); System.out.println("異常種類:"+ex); } //后置通知 @After("log()") public void after(JoinPoint jp){ System.out.println("后置通知:最后且一定執(zhí)行....."); } }
第六步:啟動項(xiàng)目
- 請求鏈接:http://localhost:8080/helloAop
- 控制臺返回的結(jié)果:
URL : http://localhost:8080/helloAop HTTP_METHOD : GET CLASS_METHOD : execution(Object com.aismall.testaop.controller.HelloController.hello()) ARGS : [] 返回通知:方法的返回值 : hello aop 后置通知:最后且一定執(zhí)行.....
- 請求鏈接:http://localhost:8080/helloError
- 控制臺返回的結(jié)果(部分):
URL : http://localhost:8080/helloError HTTP_METHOD : GET CLASS_METHOD : execution(Object com.aismall.testaop.controller.HelloController.helloError()) ARGS : [] 異常通知:方法異常時執(zhí)行..... 產(chǎn)生異常的方法execution(Object com.aismall.testaop.controller.HelloController.helloError()) 異常種類java.lang.ArithmeticException: / by zero 后置通知:最后且一定執(zhí)行.....
分析:返回通知和異常通知只會執(zhí)行一個,后置通知一定會執(zhí)行。
@PointCut 表達(dá)式詳解
- PointCut:切入點(diǎn),指哪些方法需要被執(zhí)行AOP,PointCut表達(dá)式可以有一下幾種方式 execution
execution(表達(dá)式)
- 表達(dá)式:訪問修飾符 返回值 包名.包名.包名…類名.方法名(參數(shù)列表)
- 標(biāo)準(zhǔn)的表達(dá)式寫法范例:
public void com.aismall.testaop.controller.HelloController.helloAop()
訪問修飾符可以省略
void com.aismall.testaop.controller.HelloController.helloAop()
返回值可以使用通配符*,表示任意返回值
* com.aismall.testaop.controller.HelloController.helloAop()
包名可以使用通配符,表示任意包,但是有幾級包,就需要寫幾個*.
* *.*.*.*.HelloController.helloAop()
包名可以使用…表示當(dāng)前包及其子包
* *...HelloController.helloAop()
類名和方法名都可以使用*來實(shí)現(xiàn)通配
* *..*.*()
- 參數(shù)列表:
- 可以直接寫數(shù)據(jù)類型:
- 基本類型直接寫名稱 :例如,int
- 引用類型寫包名.類名的方式 :例如,java.lang.String
- 可以使用通配符*表示任意類型,但是必須有參數(shù)
- 可以使用…表示有無參數(shù)均可,有參數(shù)可以是任意類型
全通配寫法:* ….*(…)
within
within(表達(dá)式):是用來指定類型的,指定類型中的所有方法將被攔截
- 表達(dá)式:包名.包名.包名…類名
- 標(biāo)準(zhǔn)的表達(dá)式寫法范例:
com.aismall.testaop.controller.HelloController
舉例:匹配HelloController類對應(yīng)對象的所有方法,并且只能是HelloController的對象,不能是它的子對象。
within(com.aismall.testaop.controller.HelloController)
也可以使用通配符 * :匹配com.aismall包及其子包下面的所有類的所有方法。
within(com.aismall…*)
this
- SpringAOP是基于代理的,this就代表代理對象,語法是this(type),當(dāng)生成的代理對象可以轉(zhuǎn)化為type指定的類型時表示匹配。
- this(com.aismall.testaop.controller.HelloController) 匹配生成的代理對象是HelloController類型的所有方法的外部調(diào)用
target
- SpringAOP是基于代理的,target表示被代理的目標(biāo)對象,當(dāng)被代理的目標(biāo)對象可以轉(zhuǎn)換為指定的類型時則表示匹配。
- target(com.aismall.testaop.controller.HelloController) 匹配所有被代理的目標(biāo)對象能夠轉(zhuǎn)化成HelloController類型的所有方法的外部調(diào)用。
args:
args用來匹配方法參數(shù)
args() 匹配不帶參數(shù)的方法
args(java.lang.String) 匹配方法參數(shù)是String類型的
==args(…) == 帶任意參數(shù)的方法
args(java.lang.String,…) 匹配第一個參數(shù)是String類型的,其他參數(shù)任意。最后一個參數(shù)是String的同理
@annotation:
帶有相應(yīng)注解的方法,比如對標(biāo)有@Transactional注解的方法進(jìn)行增強(qiáng)
@annotation(org.springframework.transaction.annotation.Transactional)
- @within和 @target針對類的注解
- @annotation 針對方法的注解
@args:
參數(shù)帶有相應(yīng)標(biāo)注的任意方法,比如@Transactional
@args(org.springframework.transaction.annotation.Transactional)
邏輯運(yùn)算符
表達(dá)式可由多個切點(diǎn)函數(shù)通過邏輯運(yùn)算組成
- 基本使用:使用log()方法相當(dāng)于直接使用上面的表達(dá)式
//PointCut表達(dá)式 @Pointcut("execution(public * com.aismall.testaop.controller.HelloController.*(..))") //PointCut簽名 public void log(){ }
PointCut中的運(yùn)算符:PointCut中可以使用 && 、|| 、! 運(yùn)算
@Pointcut("execution(public * com.aismall.testaop.controller.HelloController.*(..))") public void cutController(){ } @Pointcut("execution(public * com.aismall.testaop.service.UserService.*(..))") public void cutService(){ } //使用 && 運(yùn)算符,則cutAll()的作用等同于cutController 和 cutService 之和 @Pointcut("cutController() && cutService()") public void cutAll(){ }
實(shí)例2
@annotation方式
第一步:編寫一個自定義注解
//表示次注解可以標(biāo)注在類和方法上 @Target({ElementType.METHOD, ElementType.TYPE}) //運(yùn)行時生效 @Retention(RetentionPolicy.RUNTIME) public @interface MyLogAnnotation { //定義一個變量,可以接受參數(shù) String desc() default " "; }
第二步:在HelloController類中添加一個方法
@RequestMapping("helloAnnotation") //標(biāo)有這個注解的方法會被增強(qiáng) @MyLogAnnotation(desc = "@Annotation") public Object helloAnnotation() { return "hello annotation"; }
第三步:切面類
@Aspect @Component public class MyAopAnnotation { //切入點(diǎn):增強(qiáng)標(biāo)有MyLogAnnotation注解的方法 @Pointcut(value="@annotation(com.aismall.testaop.MyAnnotation.MyLogAnnotation)") //切入點(diǎn)簽名 public void logAnnotation(){ System.out.println("pointCut簽名。。。"); } //前置通知 @Before("logAnnotation()") public void deBefore(JoinPoint jp) throws Throwable { // 接收到請求,記錄請求內(nèi)容 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 記錄下請求內(nèi)容 System.out.println("URL : " + request.getRequestURL().toString()); } //返回通知 @AfterReturning(returning = "ret", pointcut = "logAnnotation()") public void doAfterReturning(Object ret) throws Throwable { // 處理完請求,返回內(nèi)容 System.out.println("返回通知:方法的返回值 : " + ret); } //異常通知 @AfterThrowing(throwing = "ex", pointcut = "logAnnotation()") public void throwss(JoinPoint jp,Exception ex){ System.out.println("異常通知:方法異常時執(zhí)行....."); System.out.println("產(chǎn)生異常的方法:"+jp); System.out.println("異常種類:"+ex); } //后置通知 @After("logAnnotation()") public void after(JoinPoint jp){ System.out.println("后置通知:最后且一定執(zhí)行....."); } }
第四步:啟動項(xiàng)目
- 請求鏈接:http://localhost:8080/helloAnnotation
- 控制臺返回的結(jié)果:
URL : http://localhost:8080/helloAnnotation
返回通知:方法的返回值 : hello annotation
后置通知:最后且一定執(zhí)行.....
環(huán)繞通知
spring的通知(Advice)中 一共有五種通知,之前已經(jīng)介紹了四種,為什么不把環(huán)繞通知和它們放在一起說,因?yàn)榄h(huán)繞通知可以把前面的四種通知都表示出來,而且環(huán)繞通知一般單獨(dú)使用
環(huán)繞通知的使用:
- Spring框架為我們提供了一個接口:ProceedingJoinPoint,該接口有一個方法proceed(),此方法就相當(dāng)于明確調(diào)用切入點(diǎn)方法。
- 該接口作為環(huán)繞通知的方法參數(shù),在程序執(zhí)行時,spring框架會為我們提供該接口的實(shí)現(xiàn)類供我們使用。
- 增強(qiáng)代碼寫在調(diào)用proceed()方法之前為前置通知,之后為返回通知,寫在catch中為異常通知,寫在finally中為后置通知
第一步:在HelloController類中添加一個方法
@RequestMapping("/helloAround") public Object helloAround(){ System.out.println("helloAround執(zhí)行了。。。"); return "hello around"; }
第二步:切面類
@Aspect @Component public class MyAroundAop { //切入點(diǎn):待增強(qiáng)的方法 @Pointcut("execution(public * com.aismall.testaop.controller.*.*(..))") //切入點(diǎn)簽名 public void logAround() { System.out.println("pointCut簽名。。。"); } //環(huán)繞通知,環(huán)繞增強(qiáng),相當(dāng)于MethodInterceptor @Around("logAround()") public Object aroundAdvice(ProceedingJoinPoint pjp) { Object rtValue = null; try { Object[] args = pjp.getArgs();//得到方法執(zhí)行所需的參數(shù) System.out.println("通知類中的aroundAdvice方法執(zhí)行了。。前置"); rtValue = pjp.proceed(args);//明確調(diào)用切入點(diǎn)方法(切入點(diǎn)方法) System.out.println("通知類中的aroundAdvice方法執(zhí)行了。。返回"); System.out.println("返回通知:"+rtValue); return rtValue; } catch (Throwable e) { System.out.println("通知類中的aroundAdvice方法執(zhí)行了。。異常"); throw new RuntimeException(e); } finally { System.out.println("通知類中的aroundAdvice方法執(zhí)行了。。后置"); } } }
第三步:啟動項(xiàng)目
- 請求鏈接:http://localhost:8080/helloAround
- 控制臺返回的結(jié)果:
通知類中的aroundAdvice方法執(zhí)行了。。前置
helloAround執(zhí)行了。。。
通知類中的aroundAdvice方法執(zhí)行了。。返回
返回通知:hello around
通知類中的aroundAdvice方法執(zhí)行了。。后置
到此這篇關(guān)于SpringBoot中的Aop用法的文章就介紹到這了,更多相關(guān)SpringBoot Aop用法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java數(shù)據(jù)結(jié)構(gòu)之二分查找法 binarySearch的實(shí)例
這篇文章主要介紹了java數(shù)據(jù)結(jié)構(gòu)之二分查找法 binarySearch的實(shí)例的相關(guān)資料,希望通過本文能幫助到大家,讓大家理解掌握這部分內(nèi)容,需要的朋友可以參考下2017-10-10java+mysql模擬實(shí)現(xiàn)銀行系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java+mysql模擬實(shí)現(xiàn)銀行系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-05-05SpringBoot Import及自定義裝配實(shí)現(xiàn)方法解析
這篇文章主要介紹了SpringBoot Import及自定義裝配實(shí)現(xiàn)方法解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-08-08Java向Runnable線程傳遞參數(shù)方法實(shí)例解析
這篇文章主要介紹了Java向Runnable線程傳遞參數(shù)方法實(shí)例解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-06-06