spring---面向切面(AOP @Pointcut 注解篇)


2.1 第一個實例

接下來,我們先看一個極簡的例子:所有的get請求被調用前在控制台輸出一句"get請求的advice觸發了"。

具體實現如下:

1、創建一個AOP切面類,只要在類上加個 @Aspect 注解即可。@Aspect 注解用來描述一個切面類,定義切面類的時候需要打上這個注解。@Component 注解將該類交給 Spring 來管理。在這個類里實現advice:

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

/**
 * @Description:
 * @author: 張重虎
 * @Date: 2022/2/8 17:02
 * @Version 1.0
 */
@Aspect
@Component
public class LogAdvice {
    /**
     * 定義一個切點:所有被 GetMapping 注解修飾的方法都會被織入 advice
     */
    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
    private void logAdvicePointcut(){}

    /**
     * 表示 logAdvice 將在目標方法執行前執行
     */
    @Before("logAdvicePointcut()")
    public void logAdvice(){
        //這里只是一個示例,你可以寫任何處理邏輯
        System.out.println("切面 @Before 執行了");
    }
}

2、創建一個接口類,內部創建一個get請求:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Description:
 * @author: 張重虎
 * @Date: 2022/2/8 17:12
 * @Version 1.0
 */
@RestController
@RequestMapping(value = "/aop")
public class AopController {

    @GetMapping("/getTest")
    public JSONObject aopTest() {
        System.out.println("Get 方法代碼執行中");
        return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}");
    }
   @PostMapping("/postTest")
    public JSONObject aopTestTwo(){
       System.out.println("Post 方法代碼執行中");
        return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}");
    }
}

項目啟動后,Get 方式請求http://localhost:8080/aop/getTest接口:
image

Post 方式請求http://localhost:8080/aop/postTest接口,控制台無輸出,證明切點確實是只針對被GetMapping修飾的方法。

2.2 第二個實例

下面我們將問題復雜化一些,該例的場景是:

1、 自定義一個注解PermissionsAnnotation

2、 創建一個切面類,切點設置為攔截所有標注PermissionsAnnotation的方法,截取到接口的參數,進行簡單的權限校驗

3、 將PermissionsAnnotation標注在測試接口類的測試接口test上

具體的實現步驟:

1、 使用@Target、@Retention、@Documented自定義一個注解:

import java.lang.annotation.*;

/**
* @Description: 自定義注解
* @author: 張重虎
* @Date: 2022/2/8 17:29
* @Version 1.0
*/


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PermissionAnnotation {
}

2、創建第一個AOP切面類,,只要在類上加個 @Aspect 注解即可。@Aspect 注解用來描述一個切面類,定義切面類的時候需要打上這個注解。@Component 注解將該類交給 Spring 來管理。在這個類里實現第一步權限校驗邏輯:

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;

/**
* @Description:
* @author: 張重虎
* @Date: 2022/2/8 17:53
* @Copyright: Xi'an Dian Tong Software Co., Ltd. All Rights Reserved.
* @Version 1.0
*/
@Aspect
@Component
@Order(1)
public class PermissionFirstAdvice {

 @Pointcut("@annotation(com.example.zhangchonghu.demo.controller.aop3.PermissionAnnotation)")
 private void permissionCheck(){}

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

     //獲取請求參數,詳見接口類
     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();
 }
}

3、創建接口類,並在目標方法上標注自定義注解 PermissionsAnnotation:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
* @Description:
* @author: 張重虎
* @Date: 2022/2/8 17:58
* @Copyright: Xi'an Dian Tong Software Co., Ltd. All Rights Reserved.
* @Version 1.0
*/
@RestController
@RequestMapping("/permission")
public class TestController {
 @RequestMapping(value = "/check", method = RequestMethod.POST)
 // 添加自定義注解
 @PermissionAnnotation()
 public JSONObject getGroupList(@RequestBody JSONObject request) {
     System.out.println("方法中打印請求參數"+request);
     return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}");
 }
}

有人會問,如果我一個接口想設置多個切面類進行校驗怎么辦?這些切面的執行順序如何管理?

很簡單,一個自定義的AOP注解可以對應多個切面類,這些切面類執行順序由@Order注解管理,該注解后的數字越小,所在切面類越先執行。

下面在實例中進行演示:

創建第二個AOP切面類,在這個類里實現第二步權限校驗:


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;

/**
 * @Description:
 * @author: 張重虎
 * @Date: 2022/2/8 18:07
 * @Version 1.0
 */
@Aspect
@Component
@Order(0)
public class PermissionSecondAdvice {
    /**
     * 自定義注解作為切面,凡是被這個注解定義的方法,都會被切面攔截
     */
    @Pointcut("@annotation(com.example.zhangchonghu.demo.controller.aop3.PermissionAnnotation)")
    private void permissionCheck(){}

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

        //獲取請求參數,詳見接口類
        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 (!"admin".equals(name)) {
            return JSON.parseObject("{\"message\":\"not admin\",\"code\":403}");
        }
        return joinPoint.proceed();
    }
}

重啟項目,繼續測試,構造兩個參數都異常的情況:
image

響應結果,表面第二個切面類執行順序更靠前:
image

2.3 總結

@Pointcut 注解,用來定義一個切面,即上文中所關注的某件事情的入口,切入點定義了事件觸發時機。
@Pointcut 注解指定一個切面,定義需要攔截的東西,這里介紹兩個常用的表達式:一個是使用 execution(),另一個是使用 annotation()。

execution表達式:

@Aspect
@Component
public class LogAspectHandler {

    /**
     * 定義一個切面,攔截 com.itcodai.course09.controller 包和子包下的所有方法
     */
    @Pointcut("execution(* com.mutest.controller..*.*(..))")
    public void pointCut() {}
}

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

第一個 * 號的位置:表示返回值類型,* 表示所有類型。

包名:表示需要攔截的包名,后面的兩個句點表示當前包和當前包的所有子包,在本例中指 com.mutest.controller包、子包下所有類的方法。

第二個 * 號的位置:表示類名,* 表示所有類。

(..):這個星號表示方法名, 表示所有的方法,后面括弧里面表示方法的參數,兩個句點表示任何參數。

annotation() 表達式:

annotation() 方式是針對某個注解來定義切面,比如我們對具有 @PostMapping 注解的方法做切面,可以如下定義切面:

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

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

還有就是如上面案例所示,針對自定義注解來定義切面:

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM