参数注解检查方法入参

参数注解检查方法入参

  springboot 中 @NotNull 等参数检查注解非常实用,优化掉了很多的重复代码。

  在开发老版本 spring 项目时,没有类似注解,所以自己实现一个类似的功能,优化代码结构。

  由于项目中没有使用统一异常处理,注解用于 Service 层,抛出的异常由 Controller 处理。

  首先自定义注解:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface CheckParams {
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
public @interface NotNull {
    String message() default "";

    ParamType type() default ParamType.VALUE;
}

  @CheckParams 注解用做切点,@NotNull 注解标注参数。切面实现:

@Aspect
@Component
public class ParamsCheckAspect {
    private static final Logger logger = Logger.getLogger(ParamsCheckAspect.class);

    ControllerUtils controllerUtils = new ControllerUtils();

    //切点
    @Pointcut("@annotation(com.inspur.paramsCheck.annotation.CheckParams)")
    public void CheckParamsAspect() {
    }

    //切面
    @Around("CheckParamsAspect()")
    public Object check(ProceedingJoinPoint joinPoint) throws IllegalAccessException, Throwable {
        Object[] args = joinPoint.getArgs();
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        Annotation[][] paramsAnnotation = method.getParameterAnnotations();
        if (null != paramsAnnotation && paramsAnnotation.length != 0) {
            for (int i = 0; i < paramsAnnotation.length; i++) {
                Annotation[] annotations = paramsAnnotation[i];
                if (null == annotations || annotations.length == 0) continue;
                int notNullPoint = -1;
                if ((notNullPoint = this.indexOf(annotations, NotNull.class.getName())) != -1) {
                    Object arg = args[i];
                    NotNull notNull = (NotNull) annotations[notNullPoint];
                    if (notNull.type() == ParamType.VALUE && isNull(arg)) {
                        throw new LackParamException(notNull.message());
                    }
                    if (notNull.type() == ParamType.BEAN) this.checkBean(arg, new HashSet<Object>());
                }
            }
        }
        return joinPoint.proceed(args);
    }


    /**
     * @Author
     * @Date 2021/10/2 下午11:53
     * @Description 递归检查对象完整性
     */
    private void checkBean(Object o, Set<Object> hisSet) throws IllegalAccessException {
        //终止条件,处理循环依赖
        if (hisSet.contains(o)) return;
        hisSet.add(o);
        Class clazz = o.getClass();
        String className = clazz.getSimpleName();
        Field[] fields = o.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (!field.isAnnotationPresent(NotNull.class)) continue;
            NotNull notNull = field.getAnnotation(NotNull.class);
            ParamType type = notNull.type();
            if (type == ParamType.VALUE && this.isNull(o, field)) {
                throw new LackParamException(className + ":" + notNull.message());
            }
            if (type == ParamType.BEAN) {
                field.setAccessible(true);
                Object bean = field.get(o);
                if (null == bean) throw new LackParamException(className + ":" + notNull.message());
                this.checkBean(bean, hisSet);
            }
        }
    }

    private int indexOf(Annotation[] arr, String annotationName) {
        for (int i = 0; i < arr.length; i++) {
            Annotation annotation = arr[i];
            String name = annotation.annotationType().getName();
            if (name.equals(annotationName)) return i;
        }
        return -1;
    }

    private boolean isNull(Object obj, Field field) throws IllegalAccessException {
        field.setAccessible(true);
        Object value = field.get(obj);
        return isNull(value);
    }

    private boolean isNull(Object obj) {
        return null == obj || obj.toString().length() == 0;
    }
}

  测试,定义 bean:

public class selectMachineVisitNumDto  extends BasePo {
    @NotNull(message = "regionCode不可为空")
    private String regionCode;
    @NotNull(message = "machineType不可为空")
    private String machineType;
    @NotNull(message = "startTime不可为空")
    private String startTime;
    @NotNull(message = "endTime不可为空")
    private String endTime;
    @NotNull(type = ParamType.BEAN)
    private TestDto testDto;

    public TestDto getTestDto() {
        return testDto;
    }

    public void setTestDto(TestDto testDto) {
        this.testDto = testDto;
    }

    public String getRegionCode() {
        return regionCode;
    }

    public void setRegionCode(String regionCode) {
        this.regionCode = regionCode;
    }

    public String getMachineType() {
        return machineType;
    }

    public void setMachineType(String machineType) {
        this.machineType = machineType;
    }

    public String getStartTime() {
        return startTime;
    }

    public void setStartTime(String startTime) {
        this.startTime = startTime;
    }

    public String getEndTime() {
        return endTime;
    }

    public void setEndTime(String endTime) {
        this.endTime = endTime;
    }
}
public class TestDto {
    @NotNull(message = "test不可为空!")
    private String test;
    @NotNull(type = ParamType.BEAN)
    private selectMachineVisitNumDto selectMachineVisitNumDto;

    public com.inspur.approval.selfstatistic.dto.selectMachineVisitNumDto getSelectMachineVisitNumDto() {
        return selectMachineVisitNumDto;
    }

    public void setSelectMachineVisitNumDto(com.inspur.approval.selfstatistic.dto.selectMachineVisitNumDto selectMachineVisitNumDto) {
        this.selectMachineVisitNumDto = selectMachineVisitNumDto;
    }

    public String getTest() {
        return test;
    }

    public void setTest(String test) {
        this.test = test;
    }

    @Override
    public String toString() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("test", test);
        jsonObject.put("selectMachineVisitNumDto", selectMachineVisitNumDto.toString());
        return jsonObject.toJSONString();
    }
}

  两个 bean 之间可形成循环依赖。Service 层,使用 @NotNull 标注参数,@CheckParams 标注方法:

    @CheckParams
    public String getMachineNum(@NotNull(type = ParamType.BEAN) selectMachineVisitNumDto dto, @NotNull(message =
            "name不可为空!") String name) throws Exception {
        JSONObject jsonObject = dto.toJSON();
        return jsonObject.toJSONString();
    }

  Controller 进行异常捕获及处理:

   @RequestMapping("test")
    @ResponseBody
    public String getMachineNum(selectMachineVisitNumDto dto, String name, String test) {
        try {
            TestDto testDto = new TestDto();
            testDto.setTest(test);
            testDto.setSelectMachineVisitNumDto(dto);
            dto.setTestDto(testDto);
            String res = selfStatisticService.getMachineNum(dto, name);
            return controllerUtils.getSuccessJson(res).toJSONString();
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return controllerUtils.getErrorJson(e.getMessage()).toJSONString();
        }
    }

  测试,dto 参数缺失:

参数注解检查方法入参

 

 

  递归检查 TestDto 中参数缺失:

参数注解检查方法入参

 

 

  两个 bean 的参数均完整,测试循环依赖情况的处理:

参数注解检查方法入参

 

   可以看到,已经跳出了对两个 dto 的检查,没有陷入循环依赖中。将单独参数 name 补充完整:

参数注解检查方法入参

 

上一篇:程序包com.sun.istack.internal不存在


下一篇:OKhttp3工具类