天天看點

Spring AOP 和 AspectJ 詳解

最近在看《Spring 實戰》,說真的第四章《面向切面程式設計的Spring》講的真心很爛,看了幾遍都不清楚到底要表達什麼,也沒有講清楚Spring AOP 和 AspectJ的差別關系,終于讓我找到了一篇文章關于 Spring AOP (AspectJ) 你該知曉的一切,寫的是真好,這裡記錄一下。

接下來舉個我自己項目代碼的例子。

我們知道,很多時候,要判斷目前使用者是否已經登入,也就是進行鑒權,登入後擷取到使用者的userId,友善進行後續操作,鑒權這裡的邏輯,我們可以寫一個Bean去處理,在每一個Controller的方法裡用以下代碼實作:

@Autowired
    private UserService userService;
    public Object buy(HttpServletRequest request){
        Integer userId = userService.getUserId(request);
        if(userId == null){
            return "鑒權失敗";
        }
        // 處理邏輯
        return "處理成功";
    }
           

這段代碼看着沒有什麼問題,但是如果這類的邏輯多了,就要出現很多重複性的代碼,滿篇的重複邏輯,這個時候面向切面程式設計就要登場了。

這裡我們通過環繞注解

@Around("@annotation(xxxxx)

的方式實作這個切面。

首先實作兩個注解。

@CheckLogin

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CheckLogin {
    /**
     * 設定為true會強制傳回 ApiResponse 401
     *
     * @return 是否強制要求登入
     */
    boolean require() default true;
}
           

這個是一個标注方法的注解,标注的方法會調用鑒權邏輯進行鑒權,注解的參數require代碼是否要求強制登入。

@UserId

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UserId {
}
           

這個是标注方法參數的注解,作用是如果鑒權成功,在切面程式設計的通知中會把标注的參數指派為鑒權後的userId,比如:

@CheckLogin
public ApiResponse ling(@UserId Integer userId){
//處理邏輯
}
           

實作切面的通知

@Aspect
@Component
public class LoginAspect {

    // 鑒權用戶端對象
    private final AuthClient authClient;
    // 鑒權失敗時傳回值
    private static final ApiResponse NOT_LOGIN_RESPONSE = ApiResponse.error(,"您還未登入,請登入後再試");

    // 構造方法
    @Autowired
    public LoginAspect(AuthClient authClient) {
        this.authClient = authClient;
    }

    // 聲明目前切面為環繞型切面,切入點為注解切入,注解CheckLogin标注的方法都進行環繞切入
    @Around("@annotation(com.cyf.annotation.CheckLogin)")
    public Object requireLogin(ProceedingJoinPoint pjp) throws Throwable {
        //擷取切入的方法簽名
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        //擷取方法
        Method method = signature.getMethod();
        //擷取注解執行個體
        CheckLogin checkLogin = method.getAnnotation(CheckLogin.class);
        Integer userId = null;
        try {
            //進行鑒權操作
            userId = authClient.getUserId();
        } catch (ThriftCallException e) {
            if (!(e instanceof AuthNoPassException)) {
                throw e;
            }
            //如果必須登入
            if (checkLogin.require()) {
                //如果傳回值是ResponseEntity
                if (method.getReturnType().isAssignableFrom(ResponseEntity.class)) {
                    return ResponseEntity.ok(NOT_LOGIN_RESPONSE);
                } else {
                    return NOT_LOGIN_RESPONSE;
                }
            }
        }
        // 擷取切點方法的參數
        Object[] args = pjp.getArgs();
        // 給切點的userId指派
        assignUserId(method, args, userId);
        return pjp.proceed(args);
    }

    //給切點的@userId注解标注的方法指派
    private void assignUserId(Method method, Object[] args, Integer userId){
        // 擷取方法參數的所有注解
        Annotation[][] annotations = method.getParameterAnnotations();
        //擷取方法參數的類型
        Class<?>[] types = method.getParameterTypes();
        for (int i = ; i < annotations.length; i++) {
            Annotation[] arr = annotations[i];
            for (Annotation a : arr) {
                Class<? extends Annotation> c = a.annotationType();
                //判斷是@UserId注解
                if (c.isAssignableFrom(UserId.class)) {
                    Class clazz = types[i];
                    if (clazz == Integer.class) {
                        args[i] = userId;//給參數指派
                        return;
                    } else if (clazz == Optional.class) {
                        args[i] = Optional.ofNullable(userId);//給Optional參數指派
                        return;
                    }
                    break;
                }
            }
        }
    }
}
           

使用切面

@RestController
@RequestMapping("test")
public class TestApi {
    @CheckLogin
    public ApiResponse buy(@UserId Integer userId){
        // 處理邏輯
        return "處理結果";
    }
}
           

是不是變得很簡單,需要鑒權的部分隻需要用@CheckLogin注解标注即可。

繼續閱讀