Springboot自定义注解实现参数验证

元注解参数解释

注解    说明
@Target    定义注解的作用目标,也就是可以定义注解具体作用在类上,方法上,还是变量上
@Retention    定义注解的保留策略,
        RetentionPolicy.SOURCE:注解仅存在于源码中在class字节码文件中不包含
        RetentionPolicy.CLASS:默认的保留策略注解会在class字节码文件中存在但运行时无法获得;
        RetentionPolicy.RUNTIME:注解会在class字节码文件中存在,在运行时可以通过反射获取到。
@Document    说明该注解将被包含在javadoc中
@Inherited    说明子类可以继承父类中的该注解

@Target类型    说明
ElementType.TYPE    接口、类、枚举、注解
ElementType.FIELD    字段、枚举的常量
ElementType.METHOD    方法
ElementType.PARAMETER    方法参数
ElementType.CONSTRUCTOR    构造函数
ElementType.LOCAL_VARIABLE    局部变量
ElementType.ANNOTATION_TYPE    注解
ElementType.PACKAGE    包

自己实现注解
目标:在controller的方法里实现自己的参数校验功能,比如我搭建了一个博客系统,现在想做一个演示的功能,用户只能操作自己的数据和信息,别人的他只能看,但是我现在有没有足够的时间去做(或者不想搞太高的复杂度)RABC,把权限传给前端让前端验证,那么简单处理就是在每个需要修改操作的接口加上验证,是否是自己的数据,每个接口写一次,是不是很烦。
那么咱们就使用注解简单搞一下。

1.引入切面支持(添加依赖)(springboot使用自定义注解也是利用aop代理)

<!-- 引入aop切面支持 -->
<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
</dependency>


2.创建自定义注解(@Target注解指定ElementType,@Retention注解指定RetentionPolicy)

package com.warmer.web.annotation;

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DomainOwner {
    String domainIdPattern() default "";//传入的参数可能是id,也可能是编码,所以定义了两个参数
    String domainPattern() default "";
}

3.创建切面类( @Pointcut注解指定@annotation注解加自定义注解的路径,@Around注解指定 有@Pointcut注解的方法,在有@Around注解的方法里进行请求参数的判断和校验)

新建切面类DomainValidAspect,告诉spring切入点(使用@Aspect注解标记)

package com.warmer.web.aspect;

import com.warmer.base.enums.ReturnStatus;
import com.warmer.base.util.R;
import com.warmer.base.util.SpringUtils;
import com.warmer.base.util.StringUtil;
import com.warmer.web.annotation.AnnotationResolver;
import com.warmer.web.annotation.DomainOwner;
import com.warmer.web.entity.KgDomain;
import com.warmer.web.security.TokenService;
import com.warmer.web.service.KnowledgeGraphService;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

@Aspect
@Component
public class DomainValidAspect {

    @Autowired
    TokenService tokenService;

    @Pointcut("@annotation(com.warmer.web.annotation.DomainOwner)")
    public void annotationPointcut() {

    }

    @Around("annotationPointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        DomainOwner domainOwner = methodSignature.getMethod().getAnnotation(DomainOwner.class);
        //获取domainId表达式,(类似形参),可以是正则,可以是具体参数,下面使用的时候讲
        String domainIdPattern = domainOwner.domainIdPattern();
        String domainPattern = domainOwner.domainPattern();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //获取当前用户的数据列表,这两行是根据具体的业务,获取数据库中的数据,可以使用SpringUtils获取对象实例,也可以是@Autowried自动注入
        KnowledgeGraphService kgService = SpringUtils.getBean(KnowledgeGraphService.class);
        List<KgDomain> myDomains = kgService.getDomainByUuid(tokenService.getLoginUserUuid(request));
        //下面就是解析参数了,具体实现靠AnnotationResolver类,下面会贴出代码
        AnnotationResolver annotationResolver = AnnotationResolver.newInstance();
        String domain = "";
        int domainId = 0;
        if(StringUtil.isNotBlank(domainIdPattern)&&StringUtil.isNotBlank(domainPattern)){
            return R.create(ReturnStatus.NoRight, "没有权限");
        }
        if(StringUtil.isNotBlank(domainIdPattern)){
            domainId = (int) annotationResolver.resolver(joinPoint, domainIdPattern);
            int finalDomainId = domainId;
            if (myDomains.stream().filter(n -> n.getId()== finalDomainId).count() == 0) {
                return R.create(ReturnStatus.DemoOperate, "不能修改别人的数据");
            }
        }
        if(StringUtil.isNotBlank(domainPattern)){
            domain = (String) annotationResolver.resolver(joinPoint, domainPattern);
            String finalDomain = domain;
            if (myDomains.stream().filter(n -> n.getName().equalsIgnoreCase(finalDomain)).count() == 0) {
                return R.create(ReturnStatus.DemoOperate, "不能修改别人的数据");
            }
        }
        return joinPoint.proceed();
    }
}

4.具体需要使用的方法(在需要进行参数校验的方法上添加@DomainOwner(我们自定义的注解))

我们定义注解的时候指定@Target({ElementType.METHOD}),作用在方法上,所以注解使用范围也只是方法

@Controller
@RequestMapping(value = "/")
public class UserManagerController {
//1.传入的是一个具体的值
	@DomainOwner(domainPattern = "#{userCode}")
    @ResponseBody
    @RequestMapping(value = "/getUserDetail")
    public R<String> getUserDetail(String userCode) {
        try {
            int domainId = (int) params.get("domainId");
            //处理自己的业务
            } catch (Exception e) {
            e.printStackTrace();
            return R.error(e.getMessage());
        }
        return R.error("操作失败");
    }
    //2.传入的是一个对象,这里User类就不贴了,属性userCode,userId之类的
    @DomainOwner(domainPattern = "#{userItem.name}")
    @ResponseBody
    @RequestMapping(value = "/saveUser")
    public R<String> saveUser(@RequestBody User userItem) {
        try {
            //处理自己的业务
            } catch (Exception e) {
            e.printStackTrace();
            return R.error(e.getMessage());
        }
        return R.error("操作失败");
    }
	//3.传入的可能是一个map,同样指定一个表达式
 	@DomainOwner(domainIdPattern = "#{params.domainId}")
    @ResponseBody
    @RequestMapping(value = "/saveNodeImage")
    public R<String> saveNodeImage(@RequestBody Map<String, Object> params) {
        try {
            int domainId = (int) params.get("domainId");
            //处理自己的业务
            } catch (Exception e) {
            e.printStackTrace();
            return R.error(e.getMessage());
        }
        return R.error("操作失败");
    }
}         

AnnotationResolver类实现

package com.warmer.web.annotation;

import java.lang.reflect.Method;
import java.util.Map;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
/**
 * 该类的作用可以把方法上的参数绑定到注解的变量中,注解的语法#{变量名}
 * 能解析类似#{userCode}或者#{userItem.name}或者{params.domainId}
 */
public class AnnotationResolver {

    private static AnnotationResolver resolver ;
    public static AnnotationResolver newInstance(){
        if (resolver == null) {
            return resolver = new AnnotationResolver();
        }else{
            return resolver;
        }
    }

    /**
     * 解析注解上的值
     * @param joinPoint
     * @param str 需要解析的字符串
     * @return
     */
    public Object resolver(JoinPoint joinPoint, String str) {

        if (str == null) return null ;

        Object value = null;
        if (str.matches("#\\{\\D*\\}")) {// 如果name匹配上了#{},则把内容当作变量
            String newStr = str.replaceAll("#\\{", "").replaceAll("\\}", "");
            if (newStr.contains(".")) { // 复杂类型
                try {
                    value = complexResolver(joinPoint, newStr);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                value = simpleResolver(joinPoint, newStr);
            }
        } else { //非变量
            value = str;
        }
        return value;
    }


    private Object complexResolver(JoinPoint joinPoint, String str) throws Exception {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String[] names = methodSignature.getParameterNames();
        Object[] args = joinPoint.getArgs();
        String[] strs = str.split("\\.");

        for (int i = 0; i < names.length; i++) {
            if (strs[0].equals(names[i])) {
                Object obj = args[i];
                //这里处理出入参数为Map的逻辑
                if(obj instanceof Map){
                   Map item=(Map) obj;
                    return item.get(strs[1]);
                }
                Method dmethod = obj.getClass().getDeclaredMethod(getMethodName(strs[1]), null);
                Object value = dmethod.invoke(args[i]);
                return getValue(value, 1, strs);
            }
        }
        return null;
    }

    private Object getValue(Object obj, int index, String[] strs) {
        try {
            if (obj != null && index < strs.length - 1) {
                Method method = obj.getClass().getDeclaredMethod(getMethodName(strs[index + 1]), null);
                obj = method.invoke(obj);
                getValue(obj, index + 1, strs);
            }
            return obj;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private String getMethodName(String name) {
        return "get" + name.replaceFirst(name.substring(0, 1), name.substring(0, 1).toUpperCase());
    }
    private Object simpleResolver(JoinPoint joinPoint, String str) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String[] names = methodSignature.getParameterNames();
        Object[] args = joinPoint.getArgs();

        for (int i = 0; i < names.length; i++) {
            if (str.equals(names[i])) {
                return args[i];
            }
        }
        return null;
    }
}

工具类SpringUtils

获取spring对象实例

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringUtils implements ApplicationContextAware{
	private static ApplicationContext applicationContext = null;

    @Override
    public void setApplicationContext(ApplicationContext arg0) throws BeansException {
        if (SpringUtils.applicationContext == null) {
            SpringUtils.applicationContext = arg0;
        }
    }

    // 获取applicationContext
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    // 通过name获取 Bean.
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    // 通过class获取Bean.
    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    // 通过name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }
}

补充

还有一点想说的,就是@Pointcut的用法,刚才我们写的是 @annotation:用于匹配当前执行方法持有指定注解的方法

/**
     * 切入点
     * 设置切入点为标记为DomainOwner的地点
     * AspectJ支持命名切入点,方法必须是返回void类型
     */
   @Pointcut("@annotation(com.warmer.web.annotation.DomainOwner)")
    public void annotationPointcut() {

    }

@Pointcut常用的还有@execution用于匹配方法执行的连接点

/**
     * 切入点
     * 设置切入点为web层
     * AspectJ支持命名切入点,方法必须是返回void类型
     */
    @Pointcut("execution(public * xxx.*.controller.*.*(..))")
    public void aopMethod() {
    }

用于个人学习记录

转载自

Springboot自定义注解实现参数验证_动力暖暖的博客-CSDN博客_springboot自定义参数校验注解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值