91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何在SpringBoot用AOP切面實現一個權限校驗

發布時間:2021-09-29 16:04:10 來源:億速云 閱讀:132 作者:柒染 欄目:編程語言

今天就跟大家聊聊有關如何在SpringBoot用AOP切面實現一個權限校驗,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。

1 理解AOP

1.1 什么是AOP

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

那么AOP為何那么重要呢?

在我們的程序中,經常存在一些系統性的需求,比如權限校驗、日志記錄、統計等,這些代碼會散落穿插在各個業務邏輯中,非常冗余且不利于維護。例如下面這個示意圖:
如何在SpringBoot用AOP切面實現一個權限校驗
有多少業務操作,就要寫多少重復的校驗和日志記錄代碼,這顯然是無法接受的。當然,用面向對象的思想,我們可以把這些重復的代碼抽離出來,寫成公共方法,就是下面這樣:

如何在SpringBoot用AOP切面實現一個權限校驗
這樣,代碼冗余和可維護性的問題得到了解決,但每個業務方法中依然要依次手動調用這些公共方法,也是略顯繁瑣。有沒有更好的方式呢?有的,那就是AOP,AOP將權限校驗、日志記錄等非業務代碼完全提取出來,與業務代碼分離,并尋找節點切入業務代碼中:

如何在SpringBoot用AOP切面實現一個權限校驗  
 

1.2 AOP體系與概念

簡單地去理解,其實AOP要做三類事:

  • 在哪里切入,也就是權限校驗等非業務操作在哪些業務代碼中執行。

  • 在什么時候切入,是業務代碼執行前還是執行后。

  • 切入后做什么事,比如做權限校驗、日志記錄等。

因此,AOP的體系可以梳理為下圖:
如何在SpringBoot用AOP切面實現一個權限校驗
一些概念詳解:

  • Pointcut:切點,決定處理如權限校驗、日志記錄等在何處切入業務代碼中(即織入切面)。切點分為execution方式和annotation方式。前者可以用路徑表達式指定哪些類織入切面,后者可以指定被哪些注解修飾的代碼織入切面。

  • Advice:處理,包括處理時機和處理內容。處理內容就是要做什么事,比如校驗權限和記錄日志。處理時機就是在什么時機執行處理內容,分為前置處理(即業務代碼執行前)、后置處理(業務代碼執行后)等。

  • Aspect:切面,即PointcutAdvice

  • Joint point:連接點,是程序執行的一個點。例如,一個方法的執行或者一個異常的處理。在 Spring AOP 中,一個連接點總是代表一個方法執行。

  • Weaving:織入,就是通過動態代理,在目標對象方法中執行處理內容的過程。

網絡上有張圖,我覺得非常傳神,貼在這里供大家觀詳:
如何在SpringBoot用AOP切面實現一個權限校驗

 

2 AOP實例

實踐出真知,接下來我們就擼代碼來實現一下AOP。完成項目已上傳至:

https://github.com/ThinkMugz/aopDemo

使用 AOP,首先需要引入 AOP 的依賴。參數校驗:這么寫參數校驗(validator)就不會被勸退了~

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
     

2.1 第一個實例

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

具體實現如下:

  1. 創建一個AOP切面類,只要在類上加個       @Aspect       注解即可。      @Aspect       注解用來描述一個切面類,定義切面類的時候需要打上這個注解。      @Component       注解將該類交給 Spring 來管理。在這個類里實現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 {
    // 定義一個切點:所有被GetMapping注解修飾的方法會織入advice
    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
    private void logAdvicePointcut() {}

 // Before表示logAdvice將在目標方法執行前執行
    @Before("logAdvicePointcut()")
    public void logAdvice(){
     // 這里只是一個示例,你可以寫任何處理邏輯
        System.out.println("get請求的advice觸發了");
    }
}
 
  1. 創建一個接口類,內部創建一個get請求:
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}");
    }
}
 

項目啟動后,請求http://localhost:8085/aop/getTest接口:

如何在SpringBoot用AOP切面實現一個權限校驗
在這里插入圖片描述

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

 

2.2 第二個實例

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

  1. 自定義一個注解PermissionsAnnotation

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

  3. PermissionsAnnotation標注在測試接口類的測試接口test

具體的實現步驟:

  1. 使用      @Target、@Retention、@Documented      自定義一個注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PermissionAnnotation{
}
 
  1. 創建第一個AOP切面類,,只要在類上加個       @Aspect       注解即可。      @Aspect       注解用來描述一個切面類,定義切面類的時候需要打上這個注解。      @Component       注解將該類交給 Spring 來管理。在這個類里實現第一步權限校驗邏輯:
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 {

 // 定義一個切面,括號內寫入第1步中自定義注解的路徑
    @Pointcut("@annotation(com.mu.demo.annotation.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();
    }
}
 
  1. 創建接口類,并在目標方法上標注自定義注解       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)
    // 添加這個注解
    @PermissionsAnnotation()
    public JSONObject getGroupList(@RequestBody JSONObject request) {
        return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}");
    }
}
 

在這里,我們先進行一個測試。首先,填好請求地址和header:

如何在SpringBoot用AOP切面實現一個權限校驗  

其次,構造正常的參數:

如何在SpringBoot用AOP切面實現一個權限校驗  

可以拿到正常的響應結果:

如何在SpringBoot用AOP切面實現一個權限校驗  

然后,構造一個異常參數,再次請求:

如何在SpringBoot用AOP切面實現一個權限校驗  

響應結果顯示,切面類進行了判斷,并返回相應結果:

如何在SpringBoot用AOP切面實現一個權限校驗  

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

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

下面在實例中進行演示:

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

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.example.demo.PermissionsAnnotation)")
   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 (!name.equals("admin")) {
           return JSON.parseObject("{\"message\":\"not admin\",\"code\":403}");
       }
       return joinPoint.proceed();
   }
}
 

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

如何在SpringBoot用AOP切面實現一個權限校驗
 

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

如何在SpringBoot用AOP切面實現一個權限校驗
 
 

3 AOP相關注解

上面的案例中,用到了諸多注解,下面針對這些注解進行詳解。

 

3.1 @Pointcut

@Pointcut 注解,用來定義一個切面,即上文中所關注的某件事情的入口,切入點定義了事件觸發時機。

@Aspect
@Component
public class LogAspectHandler {

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

@Pointcut 注解指定一個切面,定義需要攔截的東西,這里介紹兩個常用的表達式:一個是使用 execution(),另一個是使用 annotation()

更多參考:SpringBoot內容聚合

execution表達式:

以 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() {}
     

3.2 @Around

@Around注解用于修飾Around增強處理,Around增強處理非常強大,表現在:

  1. @Around可以自由選擇增強動作與目標方法的執行順序,也就是說可以在增強動作前后,甚至過程中執行目標方法。這個特性的實現在于,調用ProceedingJoinPoint參數的procedd()方法才會執行目標方法。

  2. @Around可以改變執行目標方法的參數值,也可以改變執行目標方法之后的返回值。

Around增強處理有以下特點:

  1. 當定義一個Around增強處理方法時,該方法的第一個形參必須是 ProceedingJoinPoint 類型(至少一個形參)。在增強處理方法體內,調用ProceedingJoinPointproceed方法才會執行目標方法:這就是@Around增強處理可以完全控制目標方法執行時機、如何執行的關鍵;如果程序沒有調用ProceedingJoinPointproceed方法,則目標方法不會執行。

  2. 調用ProceedingJoinPointproceed方法時,還可以傳入一個Object[ ]對象,該數組中的值將被傳入目標方法作為實參——這就是Around增強處理方法可以改變目標方法參數值的關鍵。這就是如果傳入的Object[ ]數組長度與目標方法所需要的參數個數不相等,或者Object[ ]數組元素與目標方法所需參數的類型不匹配,程序就會出現異常。

@Around功能雖然強大,但通常需要在線程安全的環境下使用。因此,如果使用普通的BeforeAfterReturning就能解決的問題,就沒有必要使用Around了。如果需要目標方法執行之前和之后共享某種狀態數據,則應該考慮使用Around。尤其是需要使用增強處理阻止目標的執行,或需要改變目標方法的返回值時,則只能使用Around增強處理了。

下面,在前面例子上做一些改造,來觀察@Around的特點。

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

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 + "}");
    }
}
 

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

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("===================開始增強處理===================");

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

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

同樣使用JMeter調用接口,傳入參數:{"id":-5,"name":"admin"},響應結果表明:@Around截取到了接口的入參,并使接口返回了切面類中的結果。
如何在SpringBoot用AOP切面實現一個權限校驗

 

3.3 @Before

@Before 注解指定的方法在切面切入目標方法之前執行,可以做一些 Log 處理,也可以做一些信息的統計,比如獲取用戶的請求 URL 以及用戶的 IP 地址等等,這個在做個人站點的時候都能用得到,都是常用的方法。例如下面代碼:

@Aspect
@Component
@Slf4j
public class LogAspectHandler {
    /**
     * 在上面定義的切面方法之前執行該方法
     * @param joinPoint jointPoint
     */
    @Before("pointCut()")
    public void doBefore(JoinPoint joinPoint) {
        log.info("====doBefore方法進入了====");

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

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

JointPoint 對象很有用,可以用它來獲取一個簽名,利用簽名可以獲取請求的包名、方法名,包括參數(通過 joinPoint.getArgs() 獲取)等。

 

3.4 @After

@After 注解和 @Before 注解相對應,指定的方法在切面切入目標方法之后執行,也可以做一些完成某方法之后的 Log 處理。

@Aspect
@Component
@Slf4j
public class LogAspectHandler {
    /**
     * 定義一個切面,攔截 com.mutest.controller 包下的所有方法
     */
    @Pointcut("execution(* com.mutest.controller..*.*(..))")
    public void pointCut() {}

    /**
     * 在上面定義的切面方法之后執行該方法
     * @param joinPoint jointPoint
     */
    @After("pointCut()")
    public void doAfter(JoinPoint joinPoint) {

        log.info("==== doAfter 方法進入了====");
        Signature signature = joinPoint.getSignature();
        String method = signature.getName();
        log.info("方法{}已經執行完", method);
    }
}
 

到這里,我們來寫個 Controller 測試一下執行結果,新建一個 AopController 如下:

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

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

啟動項目,在瀏覽器中輸入:localhost:8080/aop/csdn,觀察一下控制臺的輸出信息:

====doBefore 方法進入了====  
即將執行方法為: testAop,屬于com.itcodai.mutest.AopController包  
用戶請求的 url 為:http://localhost:8080/aop/name,ip地址為:0:0:0:0:0:0:0:1  
==== doAfter 方法進入了====  
方法 testAop 已經執行完
 

從打印出來的 Log 中可以看出程序執行的邏輯與順序,可以很直觀的掌握 @Before 和 @After 兩個注解的實際作用。

 

3.5 @AfterReturning

@AfterReturning 注解和 @After 有些類似,區別在于 @AfterReturning 注解可以用來捕獲切入方法執行完之后的返回值,對返回值進行業務邏輯上的增強處理,例如:

@Aspect
@Component
@Slf4j
public class LogAspectHandler {
    /**
     * 在上面定義的切面方法返回后執行該方法,可以捕獲返回對象或者對返回對象進行增強
     * @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("方法{}執行完畢,返回參數為:{}", classMethod, result);
        // 實際項目中可以根據業務做具體的返回值增強
        log.info("對返回參數進行業務上的增強:{}", result + "增強版");
    }
}
 

需要注意的是,在 @AfterReturning 注解 中,屬性 returning 的值必須要和參數保持一致,否則會檢測不到。該方法中的第二個入參就是被切方法的返回值,在 doAfterReturning 方法中可以對返回值進行增強,可以根據業務需要做相應的封裝。我們重啟一下服務,再測試一下:

方法 testAop 執行完畢,返回參數為:Hello CSDN  
對返回參數進行業務上的增強:Hello CSDN 增強版
     

3.6 @AfterThrowing

當被切方法執行過程中拋出異常時,會進入 @AfterThrowing 注解的方法中執行,在該方法中可以做一些異常的處理邏輯。要注意的是 throwing 屬性的值必須要和參數一致,否則會報錯。該方法中的第二個入參即為拋出的異常。

@Aspect
@Component
@Slf4j
public class LogAspectHandler {
    /**
     * 在上面定義的切面方法執行拋異常時,執行該方法
     * @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("執行方法{}出錯,異常為:{}", method, ex);
    }
}
    

看完上述內容,你們對如何在SpringBoot用AOP切面實現一個權限校驗有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

温州市| 吉林市| 邵东县| 濉溪县| 杭锦后旗| 永年县| 宁海县| 苍梧县| 安徽省| 城步| 南安市| 宜良县| 绍兴市| 炎陵县| 固安县| 新郑市| 岗巴县| 庆云县| 邻水| 久治县| 扎鲁特旗| 普陀区| 蒙城县| 罗源县| 多伦县| 富蕴县| 遵义县| 雷州市| 新巴尔虎左旗| 壤塘县| 四会市| 浮山县| 周至县| 红安县| 渭源县| 桑植县| 大厂| 奉新县| 贡觉县| 城市| 利川市|