利用参数注解+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 -
关注后,回复「题库」领取海量算法题库
回复「电子书」获取计算机相关书籍
点赞和在看就是最大的支持❤️