<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          手把手教你利用切面AOP實(shí)現(xiàn)權(quán)限校驗(yàn)!

          共 4297字,需瀏覽 9分鐘

           ·

          2022-01-26 14:29

          你知道的越多,不知道的就越多,業(yè)余的像一棵小草!

          你來(lái),我們一起精進(jìn)!你不來(lái),我和你的競(jìng)爭(zhēng)對(duì)手一起精進(jìn)!

          編輯:業(yè)余草

          blog.csdn.net/mu_wind

          推薦:https://www.xttblog.com/?p=5306

          1 理解AOP

          1.1 什么是AOP

          「AOP(Aspect Oriented Programming)」,面向切面思想,是Spring的三大核心思想之一(兩外兩個(gè):IOC-控制反轉(zhuǎn)、DI-依賴注入)。

          那么AOP為何那么重要呢?在我們的程序中,經(jīng)常存在一些系統(tǒng)性的需求,比如權(quán)限校驗(yàn)、日志記錄、統(tǒng)計(jì)等,這些代碼會(huì)散落穿插在各個(gè)業(yè)務(wù)邏輯中,非常冗余且不利于維護(hù)。例如下面這個(gè)示意圖:

          bca465347c74e1ff209dba327258a016.webpAOP權(quán)限控制

          有多少業(yè)務(wù)操作,就要寫(xiě)多少重復(fù)的校驗(yàn)和日志記錄代碼,這顯然是無(wú)法接受的。當(dāng)然,用面向?qū)ο蟮乃枷?,我們可以把這些重復(fù)的代碼抽離出來(lái),寫(xiě)成公共方法,就是下面這樣:

          4a2cf23e71a9ec722c26255a721efdf9.webp重復(fù)的代碼抽離

          這樣,代碼冗余和可維護(hù)性的問(wèn)題得到了解決,但每個(gè)業(yè)務(wù)方法中依然要依次手動(dòng)調(diào)用這些公共方法,也是略顯繁瑣。有沒(méi)有更好的方式呢?有的,那就是AOP,AOP將權(quán)限校驗(yàn)、日志記錄等非業(yè)務(wù)代碼完全提取出來(lái),與業(yè)務(wù)代碼分離,并尋找節(jié)點(diǎn)切入業(yè)務(wù)代碼中:

          66059a61f4a48d23b43bbc2194af33f9.webpAOP權(quán)限校驗(yàn)

          1.2 AOP體系與概念

          簡(jiǎn)單地去理解,其實(shí)AOP要做三類事:

          • 在哪里切入,也就是權(quán)限校驗(yàn)等非業(yè)務(wù)操作在哪些業(yè)務(wù)代碼中執(zhí)行。
          • 在什么時(shí)候切入,是業(yè)務(wù)代碼執(zhí)行前還是執(zhí)行后。
          • 切入后做什么事,比如做權(quán)限校驗(yàn)、日志記錄等。

          因此,AOP的體系可以梳理為下圖:

          8bf6727d1db1b77365778b7bb0498a79.webpAOP的體系

          一些概念詳解:

          • Pointcut:切點(diǎn),決定處理如權(quán)限校驗(yàn)、日志記錄等在何處切入業(yè)務(wù)代碼中(即織入切面)。切點(diǎn)分為execution方式和annotation方式。前者可以用路徑表達(dá)式指定哪些類織入切面,后者可以指定被哪些注解修飾的代碼織入切面。
          • Advice:處理,包括處理時(shí)機(jī)和處理內(nèi)容。處理內(nèi)容就是要做什么事,比如校驗(yàn)權(quán)限和記錄日志。處理時(shí)機(jī)就是在什么時(shí)機(jī)執(zhí)行處理內(nèi)容,分為前置處理(即業(yè)務(wù)代碼執(zhí)行前)、后置處理(業(yè)務(wù)代碼執(zhí)行后)等。
          • Aspect:切面,即PointcutAdvice
          • Joint point:連接點(diǎn),是程序執(zhí)行的一個(gè)點(diǎn)。例如,一個(gè)方法的執(zhí)行或者一個(gè)異常的處理。在 Spring AOP 中,一個(gè)連接點(diǎn)總是代表一個(gè)方法執(zhí)行。
          • Weaving:織入,就是通過(guò)動(dòng)態(tài)代理,在目標(biāo)對(duì)象方法中執(zhí)行處理內(nèi)容的過(guò)程。

          網(wǎng)絡(luò)上有張圖,我覺(jué)得非常傳神,貼在這里供大家觀詳:

          714d59579bcaae019988b7d1c5f824de.webpAOP的體系

          2 AOP實(shí)例

          實(shí)踐出真知,接下來(lái)我們就擼代碼來(lái)實(shí)現(xiàn)一下AOP。完整項(xiàng)目加我微信:xttblog2,領(lǐng)取。該項(xiàng)目是關(guān)于springboot的集成項(xiàng)目,AOP部分請(qǐng)關(guān)注【aop-demo】模塊。

          使用 AOP,首先需要引入 「AOP 的依賴」


          ????<groupId>org.springframework.bootgroupId>
          ????<artifactId>spring-boot-starter-aopartifactId>
          </dependency>

          2.1 第一個(gè)實(shí)例

          接下來(lái),我們先看一個(gè)極簡(jiǎn)的例子:「所有的get請(qǐng)求被調(diào)用前在控制臺(tái)輸出一句"get請(qǐng)求的advice觸發(fā)了"?!?/strong>

          具體實(shí)現(xiàn)如下:

          1. 「創(chuàng)建一個(gè)AOP切面類,只要在類上加個(gè) @Aspect 注解即可。@Aspect 注解用來(lái)描述一個(gè)切面類,定義切面類的時(shí)候需要打上這個(gè)注解。@Component 注解將該類交給 Spring 來(lái)管理。在這個(gè)類里實(shí)現(xiàn)advice:」
          package?com.mu.demo.advice;

          import?org.aspectj.lang.annotation.Aspect;
          import?org.aspectj.lang.annotation.Before;
          import?org.aspectj.lang.annotation.Pointcut;
          import?org.springframework.stereotype.Component;

          @Aspect
          @Component
          public?class?LogAdvice?{
          ????//?定義一個(gè)切點(diǎn):所有被GetMapping注解修飾的方法會(huì)織入advice
          ????@Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
          ????private?void?logAdvicePointcut()?{}

          ?//?Before表示logAdvice將在目標(biāo)方法執(zhí)行前執(zhí)行
          ????@Before("logAdvicePointcut()")
          ????public?void?logAdvice(){
          ?????//?這里只是一個(gè)示例,你可以寫(xiě)任何處理邏輯
          ????????System.out.println("get請(qǐng)求的advice觸發(fā)了");
          ????}
          }
          1. 創(chuàng)建一個(gè)接口類,內(nèi)部創(chuàng)建一個(gè)get請(qǐng)求:
          package?com.mu.demo.controller;

          import?com.alibaba.fastjson.JSON;
          import?com.alibaba.fastjson.JSONObject;
          import?org.springframework.web.bind.annotation.*;

          @RestController
          @RequestMapping(value?=?"/aop")
          public?class?AopController?{
          ????@GetMapping(value?=?"/getTest")
          ????public?JSONObject?aopTest()?{
          ????????return?JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}");
          ????}
          ????
          ?@PostMapping(value?=?"/postTest")
          ????public?JSONObject?aopTest2(@RequestParam("id")?String?id)?{
          ????????return?JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}");
          ????}
          }

          項(xiàng)目啟動(dòng)后,請(qǐng)求http://localhost:8085/aop/getTest接口:

          26964973795a1f32e10d4dab13c763cf.webpAOP項(xiàng)目啟動(dòng)

          請(qǐng)求http://localhost:8085/aop/postTest接口,控制臺(tái)無(wú)輸出,證明切點(diǎn)確實(shí)是只針對(duì)被GetMapping修飾的方法。

          2.2 第二個(gè)實(shí)例

          下面我們將問(wèn)題復(fù)雜化一些,該例的場(chǎng)景是:

          1. 自定義一個(gè)注解PermissionsAnnotation
          2. 創(chuàng)建一個(gè)切面類,切點(diǎn)設(shè)置為攔截所有標(biāo)注PermissionsAnnotation的方法,「截取到接口的參數(shù)」,進(jìn)行簡(jiǎn)單的權(quán)限校驗(yàn)
          3. PermissionsAnnotation標(biāo)注在測(cè)試接口類的測(cè)試接口test

          具體的實(shí)現(xiàn)步驟:

          1. 「使用@Target、@Retention、@Documented自定義一個(gè)注解:」
          @Target(ElementType.METHOD)
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          public?@interface?PermissionAnnotation{
          }
          1. 「創(chuàng)建第一個(gè)AOP切面類,,只要在類上加個(gè) @Aspect 注解即可。@Aspect 注解用來(lái)描述一個(gè)切面類,定義切面類的時(shí)候需要打上這個(gè)注解。@Component 注解將該類交給 Spring 來(lái)管理。在這個(gè)類里實(shí)現(xiàn)第一步權(quán)限校驗(yàn)邏輯:」
          package?com.example.demo;

          import?com.alibaba.fastjson.JSON;
          import?com.alibaba.fastjson.JSONObject;
          import?org.aspectj.lang.ProceedingJoinPoint;
          import?org.aspectj.lang.annotation.Around;
          import?org.aspectj.lang.annotation.Aspect;
          import?org.aspectj.lang.annotation.Pointcut;
          import?org.springframework.core.annotation.Order;
          import?org.springframework.stereotype.Component;

          @Aspect
          @Component
          @Order(1)
          public?class?PermissionFirstAdvice?{

          ?//?定義一個(gè)切面,括號(hào)內(nèi)寫(xiě)入第1步中自定義注解的路徑
          ????@Pointcut("@annotation(com.mu.demo.annotation.PermissionAnnotation)")
          ????private?void?permissionCheck()?{
          ????}

          ????@Around("permissionCheck()")
          ????public?Object?permissionCheckFirst(ProceedingJoinPoint?joinPoint)?throws?Throwable?{
          ????????System.out.println("===================第一個(gè)切面===================:"?+?System.currentTimeMillis());

          ????????//獲取請(qǐng)求參數(shù),詳見(jiàn)接口類
          ????????Object[]?objects?=?joinPoint.getArgs();
          ????????Long?id?=?((JSONObject)?objects[0]).getLong("id");
          ????????String?name?=?((JSONObject)?objects[0]).getString("name");
          ????????System.out.println("id1->>>>>>>>>>>>>>>>>>>>>>"?+?id);
          ????????System.out.println("name1->>>>>>>>>>>>>>>>>>>>>>"?+?name);

          ????????//?id小于0則拋出非法id的異常
          ????????if?(id?0)?{
          ????????????return?JSON.parseObject("{\"message\":\"illegal?id\",\"code\":403}");
          ????????}
          ????????return?joinPoint.proceed();
          ????}
          }
          1. 「創(chuàng)建接口類,并在目標(biāo)方法上標(biāo)注自定義注解 PermissionsAnnotation:」
          package?com.example.demo;

          import?com.alibaba.fastjson.JSON;
          import?com.alibaba.fastjson.JSONObject;
          import?org.springframework.web.bind.annotation.*;

          @RestController
          @RequestMapping(value?=?"/permission")
          public?class?TestController?{
          ????@RequestMapping(value?=?"/check",?method?=?RequestMethod.POST)
          ????//?添加這個(gè)注解
          ????@PermissionsAnnotation()
          ????public?JSONObject?getGroupList(@RequestBody?JSONObject?request)?{
          ????????return?JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}");
          ????}
          }

          在這里,我們先進(jìn)行一個(gè)測(cè)試。首先,填好請(qǐng)求地址和header:

          fdedcd287f46d9a20cc86fcdd899029d.webp填好請(qǐng)求地址和header

          其次,構(gòu)造正常的參數(shù):

          69534f3bd93807a3dc39371542c9e7ca.webp構(gòu)造正常的參數(shù)

          可以拿到正常的響應(yīng)結(jié)果:

          909dd76482aadff6067d495ef2225232.webp正常的響應(yīng)結(jié)果

          然后,構(gòu)造一個(gè)異常參數(shù),再次請(qǐng)求:

          e2460b63097cad1fbd42d472d10bc8a4.webp再次請(qǐng)求

          響應(yīng)結(jié)果顯示,切面類進(jìn)行了判斷,并返回相應(yīng)結(jié)果:

          65ecffed03039f349e15f9f60fac89bd.webp返回相應(yīng)結(jié)果

          「有人會(huì)問(wèn),如果我一個(gè)接口想設(shè)置多個(gè)切面類進(jìn)行校驗(yàn)怎么辦?這些切面的執(zhí)行順序如何管理」

          很簡(jiǎn)單,一個(gè)自定義的AOP注解可以對(duì)應(yīng)多個(gè)切面類,這些切面類執(zhí)行順序由@Order注解管理,該注解后的數(shù)字越小,所在切面類越先執(zhí)行。

          「下面在實(shí)例中進(jìn)行演示:」

          創(chuàng)建第二個(gè)AOP切面類,在這個(gè)類里實(shí)現(xiàn)第二步權(quán)限校驗(yàn):

          package?com.example.demo;

          import?com.alibaba.fastjson.JSON;
          import?com.alibaba.fastjson.JSONObject;
          import?org.aspectj.lang.ProceedingJoinPoint;
          import?org.aspectj.lang.annotation.Around;
          import?org.aspectj.lang.annotation.Aspect;
          import?org.aspectj.lang.annotation.Pointcut;
          import?org.springframework.core.annotation.Order;
          import?org.springframework.stereotype.Component;

          @Aspect
          @Component
          @Order(0)
          public?class?PermissionSecondAdvice?{

          ???@Pointcut("@annotation(com.mu.demo.annotation.PermissionAnnotation)")
          ???private?void?permissionCheck()?{
          ???}

          ???@Around("permissionCheck()")
          ???public?Object?permissionCheckSecond(ProceedingJoinPoint?joinPoint)?throws?Throwable?{
          ???????System.out.println("===================第二個(gè)切面===================:"?+?System.currentTimeMillis());

          ???????//獲取請(qǐng)求參數(shù),詳見(jiàn)接口類
          ???????Object[]?objects?=?joinPoint.getArgs();
          ???????Long?id?=?((JSONObject)?objects[0]).getLong("id");
          ???????String?name?=?((JSONObject)?objects[0]).getString("name");
          ???????System.out.println("id->>>>>>>>>>>>>>>>>>>>>>"?+?id);
          ???????System.out.println("name->>>>>>>>>>>>>>>>>>>>>>"?+?name);

          ???????//?name不是管理員則拋出異常
          ???????if?(!name.equals("admin"))?{
          ???????????return?JSON.parseObject("{\"message\":\"not?admin\",\"code\":403}");
          ???????}
          ???????return?joinPoint.proceed();
          ???}
          }

          重啟項(xiàng)目,繼續(xù)測(cè)試,構(gòu)造兩個(gè)參數(shù)都異常的情況:

          3ad4f486fb74b2efd1d704b48dccf9e2.webp重啟項(xiàng)目

          響應(yīng)結(jié)果,表面第二個(gè)切面類執(zhí)行順序更靠前:

          317676c68461af5c5e97484b0f39ccbf.webp切面類執(zhí)行順序

          3 AOP相關(guān)注解

          上面的案例中,用到了諸多注解,下面針對(duì)這些注解進(jìn)行詳解。

          3.1 @Pointcut

          @Pointcut 注解,用來(lái)定義一個(gè)切點(diǎn),即上文中所關(guān)注的某件事情的入口,切入點(diǎn)定義了事件觸發(fā)時(shí)機(jī)。

          @Aspect
          @Component
          public?class?LogAspectHandler?{

          ????/**
          ?????*?定義一個(gè)切面,攔截?com.mutest.controller?包和子包下的所有方法
          ?????*/

          ????@Pointcut("execution(*?com.mutest.controller..*.*(..))")
          ????public?void?pointCut()?{}
          }

          @Pointcut 注解指定一個(gè)切點(diǎn),定義需要攔截的東西,這里介紹兩個(gè)常用的表達(dá)式:「一個(gè)是使用 execution(),另一個(gè)是使用 annotation()?!?/strong>

          「execution表達(dá)式:」

          execution(* com.mutest.controller..*.*(..))) 表達(dá)式為例:

          • 第一個(gè) * 號(hào)的位置:表示返回值類型,* 表示所有類型。
          • 包名:表示需要攔截的包名,后面的兩個(gè)句點(diǎn)表示當(dāng)前包和當(dāng)前包的所有子包,在本例中指 com.mutest.controller包、子包下所有類的方法。
          • 第二個(gè) * 號(hào)的位置:表示類名,* 表示所有類。
          • *(..):這個(gè)星號(hào)表示方法名,* 表示所有的方法,后面括弧里面表示方法的參數(shù),兩個(gè)句點(diǎn)表示任何參數(shù)。

          「annotation() 表達(dá)式:」

          annotation() 方式是針對(duì)某個(gè)注解來(lái)定義切點(diǎn),比如我們對(duì)具有 @PostMapping 注解的方法做切面,可以如下定義切面:

          @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
          public?void?annotationPointcut()?{}

          然后使用該切面的話,就會(huì)切入注解是 @PostMapping 的所有方法。這種方式很適合處理 @GetMapping、@PostMapping、@DeleteMapping不同注解有各種特定處理邏輯的場(chǎng)景。

          還有就是如上面案例所示,針對(duì)自定義注解來(lái)定義切面。

          @Pointcut("@annotation(com.example.demo.PermissionsAnnotation)")
          private?void?permissionCheck()?{}

          3.2 @Around

          @Around注解用于修飾Around增強(qiáng)處理,Around增強(qiáng)處理非常強(qiáng)大,表現(xiàn)在:

          1. @Around可以自由選擇增強(qiáng)動(dòng)作與目標(biāo)方法的執(zhí)行順序,也就是說(shuō)可以在增強(qiáng)動(dòng)作前后,甚至過(guò)程中執(zhí)行目標(biāo)方法。這個(gè)特性的實(shí)現(xiàn)在于,調(diào)用ProceedingJoinPoint參數(shù)的procedd()方法才會(huì)執(zhí)行目標(biāo)方法。
          2. @Around可以改變執(zhí)行目標(biāo)方法的參數(shù)值,也可以改變執(zhí)行目標(biāo)方法之后的返回值。

          Around增強(qiáng)處理有以下特點(diǎn):

          1. 當(dāng)定義一個(gè)Around增強(qiáng)處理方法時(shí),該方法的「第一個(gè)形參必須是 ProceedingJoinPoint 類型」(至少一個(gè)形參)。在增強(qiáng)處理方法體內(nèi),調(diào)用ProceedingJoinPointproceed方法才會(huì)執(zhí)行目標(biāo)方法:這就是@Around增強(qiáng)處理可以完全控制目標(biāo)方法執(zhí)行時(shí)機(jī)、如何執(zhí)行的關(guān)鍵;如果程序沒(méi)有調(diào)用ProceedingJoinPointproceed方法,則目標(biāo)方法不會(huì)執(zhí)行。
          2. 調(diào)用ProceedingJoinPointproceed方法時(shí),還可以傳入一個(gè)Object[ ]對(duì)象,該數(shù)組中的值將被傳入目標(biāo)方法作為實(shí)參——「這就是Around增強(qiáng)處理方法可以改變目標(biāo)方法參數(shù)值的關(guān)鍵」。這就是如果傳入的Object[ ]數(shù)組長(zhǎng)度與目標(biāo)方法所需要的參數(shù)個(gè)數(shù)不相等,或者Object[ ]數(shù)組元素與目標(biāo)方法所需參數(shù)的類型不匹配,程序就會(huì)出現(xiàn)異常。

          @Around功能雖然強(qiáng)大,但通常需要在線程安全的環(huán)境下使用。因此,如果使用普通的BeforeAfterReturning就能解決的問(wèn)題,就沒(méi)有必要使用Around了。如果需要目標(biāo)方法執(zhí)行之前和之后共享某種狀態(tài)數(shù)據(jù),則應(yīng)該考慮使用Around。尤其是需要使用增強(qiáng)處理阻止目標(biāo)的執(zhí)行,或需要改變目標(biāo)方法的返回值時(shí),則只能使用Around增強(qiáng)處理了。

          下面,在前面例子上做一些改造,來(lái)觀察@Around的特點(diǎn)。

          自定義注解類不變。首先,定義接口類:

          package?com.example.demo;

          import?com.alibaba.fastjson.JSON;
          import?com.alibaba.fastjson.JSONObject;
          import?org.springframework.web.bind.annotation.*;

          @RestController
          @RequestMapping(value?=?"/permission")
          public?class?TestController?{
          ????@RequestMapping(value?=?"/check",?method?=?RequestMethod.POST)
          ????@PermissionsAnnotation()
          ????public?JSONObject?getGroupList(@RequestBody?JSONObject?request)?{
          ????????return?JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200,\"data\":"?+?request?+?"}");
          ????}
          }

          唯一切面類(前面案例有兩個(gè)切面類,這里只需保留一個(gè)即可):

          package?com.example.demo;

          import?com.alibaba.fastjson.JSONObject;
          import?org.aspectj.lang.ProceedingJoinPoint;
          import?org.aspectj.lang.annotation.Around;
          import?org.aspectj.lang.annotation.Aspect;
          import?org.aspectj.lang.annotation.Pointcut;
          import?org.springframework.core.annotation.Order;
          import?org.springframework.stereotype.Component;

          @Aspect
          @Component
          @Order(1)
          public?class?PermissionAdvice?{

          ????@Pointcut("@annotation(com.example.demo.PermissionsAnnotation)")
          ????private?void?permissionCheck()?{
          ????}


          ????@Around("permissionCheck()")
          ????public?Object?permissionCheck(ProceedingJoinPoint?joinPoint)?throws?Throwable?{
          ????????System.out.println("===================開(kāi)始增強(qiáng)處理===================");

          ????????//獲取請(qǐng)求參數(shù),詳見(jiàn)接口類
          ????????Object[]?objects?=?joinPoint.getArgs();
          ????????Long?id?=?((JSONObject)?objects[0]).getLong("id");
          ????????String?name?=?((JSONObject)?objects[0]).getString("name");
          ????????System.out.println("id1->>>>>>>>>>>>>>>>>>>>>>"?+?id);
          ????????System.out.println("name1->>>>>>>>>>>>>>>>>>>>>>"?+?name);

          ??//?修改入?yún)?/span>
          ????????JSONObject?object?=?new?JSONObject();
          ????????object.put("id",?8);
          ????????object.put("name",?"lisi");
          ????????objects[0]?=?object;
          ??
          ??//?將修改后的參數(shù)傳入
          ????????return?joinPoint.proceed(objects);
          ????}
          }

          同樣使用JMeter調(diào)用接口,傳入?yún)?shù):{"id":-5,"name":"admin"},響應(yīng)結(jié)果表明:@Around截取到了接口的入?yún)?,并使接口返回了切面類中的結(jié)果。

          0d168f597b34e7c837dff017c2e1940a.webpJMeter調(diào)用接口

          3.3 @Before

          @Before 注解指定的方法在切面切入目標(biāo)方法之前執(zhí)行」,可以做一些 Log 處理,也可以做一些信息的統(tǒng)計(jì),比如獲取用戶的請(qǐng)求 URL 以及用戶的 IP 地址等等,這個(gè)在做個(gè)人站點(diǎn)的時(shí)候都能用得到,都是常用的方法。例如下面代碼:

          @Aspect
          @Component
          @Slf4j
          public?class?LogAspectHandler?{
          ????/**
          ?????*?在上面定義的切面方法之前執(zhí)行該方法
          ?????*?@param?joinPoint?jointPoint
          ?????*/

          ????@Before("pointCut()")
          ????public?void?doBefore(JoinPoint?joinPoint)?{
          ????????log.info("====doBefore方法進(jìn)入了====");

          ????????//?獲取簽名
          ????????Signature?signature?=?joinPoint.getSignature();
          ????????//?獲取切入的包名
          ????????String?declaringTypeName?=?signature.getDeclaringTypeName();
          ????????//?獲取即將執(zhí)行的方法名
          ????????String?funcName?=?signature.getName();
          ????????log.info("即將執(zhí)行方法為:?{},屬于{}包",?funcName,?declaringTypeName);

          ????????//?也可以用來(lái)記錄一些信息,比如獲取請(qǐng)求的?URL?和?IP
          ????????ServletRequestAttributes?attributes?=?(ServletRequestAttributes)?RequestContextHolder.getRequestAttributes();
          ????????HttpServletRequest?request?=?attributes.getRequest();
          ????????//?獲取請(qǐng)求?URL
          ????????String?url?=?request.getRequestURL().toString();
          ????????//?獲取請(qǐng)求?IP
          ????????String?ip?=?request.getRemoteAddr();
          ????????log.info("用戶請(qǐng)求的url為:{},ip地址為:{}",?url,?ip);
          ????}
          }

          JointPoint 對(duì)象很有用,可以用它來(lái)獲取一個(gè)簽名,利用簽名可以獲取請(qǐng)求的包名、方法名,包括參數(shù)(通過(guò) joinPoint.getArgs() 獲?。┑?。

          3.4 @After

          @After 注解和 @Before 注解相對(duì)應(yīng),指定的方法在切面切入目標(biāo)方法之后執(zhí)行,也可以做一些完成某方法之后的 Log 處理。

          @Aspect
          @Component
          @Slf4j
          public?class?LogAspectHandler?{
          ????/**
          ?????*?定義一個(gè)切面,攔截?com.mutest.controller?包下的所有方法
          ?????*/

          ????@Pointcut("execution(*?com.mutest.controller..*.*(..))")
          ????public?void?pointCut()?{}

          ????/**
          ?????*?在上面定義的切面方法之后執(zhí)行該方法
          ?????*?@param?joinPoint?jointPoint
          ?????*/

          ????@After("pointCut()")
          ????public?void?doAfter(JoinPoint?joinPoint)?{

          ????????log.info("====?doAfter?方法進(jìn)入了====");
          ????????Signature?signature?=?joinPoint.getSignature();
          ????????String?method?=?signature.getName();
          ????????log.info("方法{}已經(jīng)執(zhí)行完",?method);
          ????}
          }

          到這里,我們來(lái)寫(xiě)個(gè) Controller 測(cè)試一下執(zhí)行結(jié)果,新建一個(gè) AopController 如下:

          @RestController
          @RequestMapping("/aop")
          public?class?AopController?{

          ????@GetMapping("/{name}")
          ????public?String?testAop(@PathVariable?String?name)?{
          ????????return?"Hello?"?+?name;
          ????}
          }

          啟動(dòng)項(xiàng)目,在瀏覽器中輸入:localhost:8080/aop/csdn,觀察一下控制臺(tái)的輸出信息:

          ====doBefore?方法進(jìn)入了====??
          即將執(zhí)行方法為:?testAop,屬于com.itcodai.mutest.AopController包??
          用戶請(qǐng)求的 url 為:http://localhost:8080/aop/name,ip地址為:0:0:0:0:0:0:0:1 ?
          ====?doAfter?方法進(jìn)入了====??
          方法?testAop?已經(jīng)執(zhí)行完

          從打印出來(lái)的 Log 中可以看出程序執(zhí)行的邏輯與順序,可以很直觀的掌握 @Before@After 兩個(gè)注解的實(shí)際作用。

          3.5 @AfterReturning

          @AfterReturning 注解和 @After 有些類似,區(qū)別在于 @AfterReturning 注解可以用來(lái)捕獲切入方法執(zhí)行完之后的返回值,對(duì)返回值進(jìn)行業(yè)務(wù)邏輯上的增強(qiáng)處理,例如:

          @Aspect
          @Component
          @Slf4j
          public?class?LogAspectHandler?{
          ????/**
          ?????*?在上面定義的切面方法返回后執(zhí)行該方法,可以捕獲返回對(duì)象或者對(duì)返回對(duì)象進(jìn)行增強(qiáng)
          ?????*?@param?joinPoint?joinPoint
          ?????*?@param?result?result
          ?????*/

          ????@AfterReturning(pointcut?=?"pointCut()",?returning?=?"result")
          ????public?void?doAfterReturning(JoinPoint?joinPoint,?Object?result)?{

          ????????Signature?signature?=?joinPoint.getSignature();
          ????????String?classMethod?=?signature.getName();
          ????????log.info("方法{}執(zhí)行完畢,返回參數(shù)為:{}",?classMethod,?result);
          ????????//?實(shí)際項(xiàng)目中可以根據(jù)業(yè)務(wù)做具體的返回值增強(qiáng)
          ????????log.info("對(duì)返回參數(shù)進(jìn)行業(yè)務(wù)上的增強(qiáng):{}",?result?+?"增強(qiáng)版");
          ????}
          }

          需要注意的是,在 @AfterReturning 注解 中,屬性 returning 的值必須要和參數(shù)保持一致,否則會(huì)檢測(cè)不到。該方法中的第二個(gè)入?yún)⒕褪潜磺蟹椒ǖ姆祷刂?,?doAfterReturning 方法中可以對(duì)返回值進(jìn)行增強(qiáng),可以根據(jù)業(yè)務(wù)需要做相應(yīng)的封裝。我們重啟一下服務(wù),再測(cè)試一下:

          方法 testAop 執(zhí)行完畢,返回參數(shù)為:Hello CSDN ?
          對(duì)返回參數(shù)進(jìn)行業(yè)務(wù)上的增強(qiáng):Hello CSDN 增強(qiáng)版

          3.6 @AfterThrowing

          當(dāng)被切方法執(zhí)行過(guò)程中拋出異常時(shí),會(huì)進(jìn)入 @AfterThrowing 注解的方法中執(zhí)行,在該方法中可以做一些異常的處理邏輯。要注意的是 throwing 屬性的值必須要和參數(shù)一致,否則會(huì)報(bào)錯(cuò)。該方法中的第二個(gè)入?yún)⒓礊閽伋龅漠惓!?/p>

          @Aspect
          @Component
          @Slf4j
          public?class?LogAspectHandler?{
          ????/**
          ?????*?在上面定義的切面方法執(zhí)行拋異常時(shí),執(zhí)行該方法
          ?????*?@param?joinPoint?jointPoint
          ?????*?@param?ex?ex
          ?????*/

          ????@AfterThrowing(pointcut?=?"pointCut()",?throwing?=?"ex")
          ????public?void?afterThrowing(JoinPoint?joinPoint,?Throwable?ex)?{
          ????????Signature?signature?=?joinPoint.getSignature();
          ????????String?method?=?signature.getName();
          ????????//?處理異常的邏輯
          ????????log.info("執(zhí)行方法{}出錯(cuò),異常為:{}",?method,?ex);
          ????}
          }

          完整的項(xiàng)目加我微信:xttblog2,我分享給你。聽(tīng)說(shuō)這么做的人2021年都升職加薪了!

          以上就是AOP的全部?jī)?nèi)容。通過(guò)幾個(gè)例子就可以感受到,AOP 的便捷之處。歡迎大家指出文中不足之處,喜歡的朋友點(diǎn)贊收藏!

          瀏覽 33
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    亚洲在一一线线电影 | 免费在线无毒av毛片久 | 日韩V在线观看 | 成人伊人AV | 国产AA级片久久 |