架构师修行录

利用参数注解+AOP对方法进行拦截校验

今天写了一个店铺权限校验的拦截代码,不敢私藏,分享给大家。

需求背景

为了防止商家的店铺账号登录商家中心后越权操作其它店铺,需要对店铺的账号进行校验,传入的店铺编码shopCode后调用店铺服务的接口进行校验。

嗯,听起来不难,最简单的方式是在Controller方法进来,显式调用校验方法即可,但真要这么写的话,Spring岂不是白学了。。。

咱们稍微换一下思路,参考SpringMVC的 @PathVariable、javax包下的 @Valid 功能来实现一遍。

涉及的技术及原理

无疑是自定义注解+AOP,底层涉及到动态代理与反射机制,这两个的组合适用于很多场景,我列了一个清单:

  • 日志打印

  • 参数校验(权限校验)

  • 分布式锁

  • 流量控制(滑动窗口算法)

  • 事务控制

  • 结合ThreadLocal的隐式参数传递

    ……

我们可以把它理解为代码增强,以一种低侵入的方式,实现代码解耦,关注点分离。

废话不多说,直接上代码。

定义一个参数注解类

「ValidShop.java」

import java.lang.annotation.*;

/**
 * 校验店铺注解
 */
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ValidShop {
    String value() default "shopCode";
}

AOP拦截器实现

「ShopValidator.java」

import org.apache.commons.lang.ArrayUtils;
import org.apache.logging.log4j.util.Strings;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Optional;

/**
 * 店铺校验器
 */
@Aspect
@Component
public class ShopValidator {

    /**
     * 拦截注解了@RestController的对象
     */
    @Around("@within(org.springframework.web.bind.annotation.RestController)")
    public Object aroundController(ProceedingJoinPoint pjp) throws Throwable {
        Optional<String> shopCode = getAnnotatedValue(pjp);
        if (shopCode.isPresent()) {
            // 校验逻辑
            Result validateResult = ShopHelper.validateShopCode(shopCode.get());
            if (validateResult.isFailed()) {
                return validateResult;
            }
        }
        // 执行目标方法
        return pjp.proceed();
    }

    /**
     * 返回被注解的字符串参数值或对象参数的属性值
     */
    private static Optional<String> getAnnotatedValue(ProceedingJoinPoint pjp) throws NoSuchFieldException, IllegalAccessException {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        //获取方法上所有参数上的注解
        Annotation[][] parameterAnnotations = signature.getMethod().getParameterAnnotations();
        for (Annotation[] parameterAnnotation : parameterAnnotations) {
            int paramIndex = ArrayUtils.indexOf(parameterAnnotations, parameterAnnotation);
            for (Annotation annotation : parameterAnnotation) {
                if (annotation instanceof ValidShop) {
                    ValidShop annotated = (ValidShop) annotation;
                    Object[] args = pjp.getArgs();
                    Object param = args[paramIndex];
                    if (param != null) {
                        if (param instanceof String) {
                            // 参数是String类型,直接返回
                            return Optional.of((String) param);
                        } else {
                            // 参数是类类型,不能注解到其它基本类型上,只能是String或Object类
                            String fieldName = annotated.value();
                            Field field = param.getClass().getDeclaredField(fieldName);
                            field.setAccessible(true);
                            Object fieldValue = field.get(param);
                            // 属性是String类型,强转返回
                            if (fieldValue instanceof String) {
                                return Optional.of((String) fieldValue);
                            }
                        }
                    }
                    // 条件不符合,返回空字符串
                    return Optional.of(Strings.EMPTY);
                }
            }
        }
        // 找不到被注解的参数或对象属性
        return Optional.empty();
    }
}

Controller方法使用

「XXXController.java」

/**
 * 方法一:使用字符串接收并校验
 */
@GetMapping("/{shopCode}")
public Result<Void> xxx(@PathVariable("shopCode") @ValidShop String shopCode) {
    // shopCode校验通过
    return Result.ok();
}

/**
 * 方法二:使用对象接收并校验,ShopQueryParam类定义了shopCode属性
 */
@PostMapping("/page")
public Result<Void> xxx(@RequestBody @ValidShop ShopQueryParam param) {
    // shopCode校验通过
    return Result.ok();
}


本文的核心在于获取被注解了的方法参数,通过反射获取参数值,然后进行业务处理。此外,注解还能定义在属性上、类上、方法上,这些实现方式都能百度到一大堆,动手干就行了。

觉得有用的来一波「Ctrl+C」。

- EOF -

推荐阅读  点击标题可跳转

1、为什么说高手都有“上游思维”?

2、跨库聚合全文搜索的三种存储方案

3、微服务设计的四大原则

4、Feign两种用法的优劣对比分析

Image

关注后,回复「题库」领取海量算法题库

回复「电子书」获取计算机相关书籍

点赞和在看就是最大的支持❤️