jiaowojiangge 发表于 2022-7-19 12:23

自定义注解对接口传入参数进行空判断

**前言:**
> 本来项目是Springboot的,用的是 validation 对接口传入的参数进行判空处理,自己测试没得问题(测试服务器上也没得问题),但是技术经理最后需要打成war包(springboot多为jar包)部署到Tomcat上,但是发现打包为war包之后validation就突然失效了,略微查找之后没得找到问题根源,当时由于时间紧张,@RequestParam不能满足需求,就自己写了自定义的注解通过切面暂时实现,感觉挺有意思,就记录一下:直接上代码

#### 一. 判断入参不为空的注解
##### 1.1 : 定义注解 StringNullRegex (名字自己随意取)

        import java.lang.annotation.*;
       
        /**
       *   @Description         判断接口入参不能为空的自定义注解
       *   @AuThor 江
       */
        @Target(ElementType.METHOD)   // 作用于方法
        @Retention(RetentionPolicy.RUNTIME)
        public @interface StringNullRegex {
       
        }
##### 1.2 : 注解功能实现 StringNullRegexAspect
```java
import com.example.waitdemo.utils.HttpResult;
import com.example.waitdemo.utils.StringNullUtil;
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.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;

/**
* @Description       StringNullRegex注解的实现
* @ClassName         StringNullRegexAspect
*/
@Aspect   // 切面
@Component// 表示这是一个bean,由Spring进行管理 或者说输入spring
public class StringNullRegexAspect {

    // 配置织入点   @annotation 为自定义的注解的位置(路径)
    @Pointcut("@annotation(com.example.waitdemo.config.StringNullRegex)")
    public void annotationPointcut() {
    }
               
        /**
       *
       */
        @Around("annotationPointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
      MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
      Method method = methodSignature.getMethod();
                // 通过方法获取参数及其内容的 map
      Map<String, String> parameter = getParameter(method, joinPoint.getArgs());
                // 对参数进行判空处理
      for (String key : parameter.keySet()) {
            if (StringNullUtil.isEmpty(parameter.get(key))){
                return HttpResult.newError(key + "参数不能为空!");
            }
      }

      return joinPoint.proceed();
    }
               
                // 获取参数及对应数据
                private Map<String, String> getParameter(Method method, Object[] args) {
      Map<String, String> argList = newHashMap<>();
      Parameter[] parameters = method.getParameters();
      
      for (int i = 0; i < parameters.length; i++) {
            
            argList.put(parameters.getName(), String.valueOf(args));
      }
      if (argList.size() == 0) {
            return null;
      } else if (argList.size() == 1) {
            return argList;
      } else {
            return argList;
      }
    }

}
```


#### 二. 判断入参为对象 不为空的注解
##### 2.1 : 定义注解 NotNotNull (名字自己随意取,这里是为了和 NotNull 区分开)
```java
/**
* @Description   配合 ClassNotNull 进行实体类验证
*                  作用于实体类的字段上边
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)   // 作用于字段
public @interface NotNotNull {
                Stringmessage() default "";
}
```
##### 2.2 : 定义注解 ClassNotNull
```
/**
* @Description      配合 NotNotNull 实体类验证
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClassNotNull {
}
```
##### 2.3 : 注解功能实现 ClassNotNull
```java
import com.example.waitdemo.utils.HttpResult;
import com.example.waitdemo.utils.StringNullUtil;
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.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

/**
* @Description
* @ClassName ClassNotNullAspect
*/
@Aspect   // 切面
@Component// 表示这是一个bean,由Spring进行管理 或者说输入spring
public class ClassNotNullAspect {

    // 配置织入点
    @Pointcut("@annotation(com.example.waitdemo.config.ClassNotNull)")
    public void ClassNotNullPointcut() {
    }
               
        @Around("ClassNotNullPointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
      MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
      Method method = methodSignature.getMethod();
      //System.out.println("---------");
      Object arg = joinPoint.getArgs();
      Parameter[] parameters = method.getParameters();
      //获取class对象
      Class<?> aClass = parameters.getType();

      //获取当前对象所有属性使用带Declared的方法可访问private属性
      Field[] declaredFields = aClass.getDeclaredFields();
      for(Field field:declaredFields){
            //开启访问权限
            field.setAccessible(true);
            //使用此方法 field.get(Object obj) 可以获取当前对象这个列的值
            Object o = field.get(arg);
            Annotation annotation = field.getDeclaredAnnotation(NotNotNull.class);
            //如果没有设置当前注解 不用校验
            if(annotation == null){
                continue;
            }
            //如果设置了当前注解,但是没有值,抛出异常
            if(StringNullUtil.isEmpty(String.valueOf(o))){
                //获取注解接口对象
                NotNotNull notNull = (NotNotNull)annotation;
                if(StringNullUtil.isNotEmpty(notNull.message())){
                  System.out.println("notNull.message() = " + notNull.message());
                  //设置了注解message值 直接返回
                                                                                // 这里又两种返回方法 ,
                                                                                // 1: 通过自定义错误,然后进行全局拦截
                  //throw new ClassNotNullException(notNull.message());
                                                                                // 2: 自己包装的返回参数 (通过枚举等)
                  return HttpResult.newError(notNull.message());
                }else{
                  System.out.println("field.getName() = " + field.getName()+" is null");
                  //没有设置可以拼接
                  //throw new ClassNotNullException(field.getName()+" is null");
                  return HttpResult.newError(field.getName()+" is null");
                }
            }
      }
      return joinPoint.proceed();
    }
}
```
**自定义的注解已经完成,准备使用**
##### 三. 定义用户类:
**自定义注解 NotNotNull的使用位置**

```java
import com.example.waitdemo.config.NotNotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
* TODO
*
* @ClassName User
* @Author
* @Date
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Component
public class UserProperty {

    @NotNotNull(message = "名字不能为空!")
    private String name;
    private Integer age;
    @NotNotNull(message = "qq is null")
    private Integer qq;
               
}
```
##### 四. controller 中使用自定义注解
``` java
/**
* @ClassName WaitController
*/
@Log4j2
@RestController
@RequestMapping("/wait")
public class WaitController {

    @RequestMapping(value = "/test_string_null",method = RequestMethod.GET)
    @StringNullRegex
    public HttpResult testStringNullRegex(String name, Integer age){
      
      System.out.println("名字 = " + name);
   
      System.out.println("年龄 = " + age);

      return HttpResult.newSuccess();
    }

    @RequestMapping(value = "/testTwo",method = RequestMethod.GET)
    @ClassNotNull
    public HttpResult testTwo(UserProperty userProperty){
       
      String name = userProperty.getName();
      System.out.println("名字 = " + name);
      Integer age = userProperty.getAge();
      System.out.println("年龄 = " + age);
      Integer qq = userProperty.getQq();
      System.out.println("QQ = " + qq);

      return HttpResult.newSuccess();
    }
}
```
**其他问题:**
> 到这里基本的功能实现了,但是新问题:
> 1. 不支持 多参数中可以为空,可以不为空的问题
>
> 解释: 这个没有用到就没得测试,对象注解中算实现了,但是不太合格,通过自定义注解应该也可以实现,感兴趣的可以试一下^_^

maozzs 发表于 2022-7-22 20:15

钻研的精神是可佳的{:1_921:}。不过相比于spring框架自带的非空注解。这么做的优势在哪里哟~
页: [1]
查看完整版本: 自定义注解对接口传入参数进行空判断