文章目录

  • 一、概述
  • 二、实现细节
    • 1. 自定义注解
    • 2. 工具类
    • 3. Aop
  • 三、使用方法
    • 1. 定义Aop
    • 2. Model中标识必传字段和嵌套Model
    • 3. Controller层添加自定义注解

一、概述

后端服务提供的接口,通常需要校验必传参数。Url中的必传参数不需要在代码里单独校验,因为基于Spring注解,缺少必传参数的接口将无法访问。但是当请求入参是一个实体类时,则需要单独对实体类内必传字段进行校验。

本方案就是用来解决这个问题的,使用者无需在代码里单独进行校验,只需要加上一个自定义注解,程序即可自动完成入参Model必传字段校验。即使入参Model内嵌套其他需要校验的Model或Model集合,该解决方案也可以以递归方式完成多层校验。

项目基于Swagger生成接口文档,我们也是通过Swagger@ApiModelProperty注解通知前端哪些属性是必传的。因此,该方案选择使用@ApiModelProperty注解的required参数标识必传字段,也算水到渠成、名正言顺。

以下将通过两部分内容,分别阐述该方案的实现细节,以及使用方法。

二、实现细节

1. 自定义注解

通过自定义注解,标识需要校验的入参Model,标识入参Model内需要校验的嵌套Model。代码如下:

package com.sj.annotation.check;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 将该注解加在入参<b>Model</b>上之后,系统会自动校验该<b>Model</b>中的必传字段,如果必传字段未传值,将会封装异常返回。* <br><br>* 请对<b>Controller</b>层的入参Model使用该注解,因为系统扫描范围设定在<b>Controller</b>层。* <br><br>* 示例:* <pre>*     <code>@PostMapping</code>*     public OperationResult<CommonReturnBean> plant(@CheckRequiredArgs @RequestBody Tree tree) throws Exception {**     }* </pre>* 此外,该注解还可以加在入参Model内嵌套Model属性上,用来标识该嵌套Model需要校验。* @author mark* @date 2020/10/06*/
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckRequiredArgs {}

2. 工具类

入参Model中必传字段校验,以及入参Model内嵌套Model中必传字段校验,实际上是通过该工具类完成的,该工具类内包含该方案实现的主要代码逻辑。代码如下:

package com.sj.util.check;import com.sj.annotation.check.CheckRequiredArgs;
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Collection;
import java.util.Map;/*** 校验入参model中的必传字段<br><br>** 必传字段由Swagger注解@ApiModelProperty的required参数等于true标识** @author mark* @date 2020/10/06*/
@Slf4j
public class CheckRequiredArgsUtils {/*** 校验请求入参<br><br>* 如果目标方法有{@code @CheckRequiredArgs}注解修饰的入参Model,且该Model有未赋值的必传字段,则返回该字段的{@code @ApiModelProperty}注解的{@code value}参数值(如果{@code value}参数值为空,返回该参数名);其余情况返回<b>null</b>* @param joinPoint*/public static String check(ProceedingJoinPoint joinPoint) {try {// 1.匹配Class<?> type = getType(joinPoint);if (type == null) {return null;}// 2.校验Object[] args = joinPoint.getArgs();if (args == null || args.length == 0) {return null;}for (Object arg : args) {if (arg.getClass() == type) {return requiredArgsNonEmpty(arg);}}} catch (Exception e) {log.error(e.getMessage(), e);}// 3.return null;}/*** 获取目标方法的参数列表中{@code @CheckRequiredArgs}注解修饰的参数的参数类型<br><br>* 如果未匹配到,返回<b>null</b>* @param joinPoint*/private static Class<?> getType(ProceedingJoinPoint joinPoint) {Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();if (method == null) {return null;}Parameter[] params = method.getParameters();if (params == null || params.length == 0) {return null;}for (Parameter param : params) {Annotation[] annotations = param.getDeclaredAnnotations();if (annotations == null && annotations.length == 0) {continue;}for (Annotation annotation : annotations) {if (annotation instanceof CheckRequiredArgs) {return param.getType();}}}return null;}/*** 校验必传字段非空<br><br>* 通过校验,返回null。否则,返回未赋值的必传字段的{@code @ApiModelProperty}注解的{@code value}参数值(如果{@code value}参数值为空,返回该参数名)* @param obj*/private static String requiredArgsNonEmpty(Object obj) throws IllegalAccessException {// 递归调用时obj可能为nullif (obj == null) {return null;}Field[] fields = obj.getClass().getDeclaredFields();if (fields == null || fields.length == 0) {return null;}for (Field field : fields) {ApiModelProperty apiModelProperty = field.getAnnotation(ApiModelProperty.class);if (apiModelProperty != null && apiModelProperty.required()) {try {field.setAccessible(true);Object value = field.get(obj);// 判空boolean isEmpty = isFieldEmpty(value);if (isEmpty) {if (StringUtils.isNotBlank(apiModelProperty.value())) {return apiModelProperty.value();} else {return field.getName();}}} catch (IllegalAccessException e) {log.error(e.getMessage(), e);}}// 嵌套model必传字段校验/** 嵌套model必传字段校验引发了几个问题需要思考:*  1.怎么知道当前属性是嵌套model? 在嵌套model上使用@CheckRequiredArgs注解标识*  2.嵌套model本身是否需要判断是否是必传字段?  需要,因为如果它是一个必传字段,那么就不应该允许传一个null进来*  3.如果model本身非必传,该model的必传字段判空是否还有意义? 分情况,如果它是null,那么它的必传字段的校验也变得没有意义;如果它不是null,那么它的必传字段则是需要校验的*  4.经过第1、2步的判断之后,无论model本身是否必传,只要model非null,就递归判断* 代码中逻辑,按照以上思考结论实现*/CheckRequiredArgs checkRequiredArgs = field.getAnnotation(CheckRequiredArgs.class);if (checkRequiredArgs != null) {field.setAccessible(true);Object value = field.get(obj);// 递归if (value instanceof Collection) {if (CollectionUtils.isNotEmpty((Collection) value)) {for (Object element : ((Collection) value)) {String checkResult = requiredArgsNonEmpty(element);if (checkResult != null) {return checkResult;}}}} else {String checkResult = requiredArgsNonEmpty(value);if (checkResult != null) {return checkResult;}}}}return null;}/*** Field判空*/private static boolean isFieldEmpty(Object value) {if (value == null) {return true;}// 集合if (value instanceof Collection && CollectionUtils.isEmpty((Collection) value)) {return true;}// 数组if (value.getClass().isArray() && ((Object[]) value).length == 0) {return true;}// Mapif (value instanceof Map && CollectionUtils.isEmpty(((Map) value).keySet())) {return true;}// 字符串if (value instanceof String && StringUtils.isBlank(String.valueOf(value))) {return true;}return false;}
}

3. Aop

利用Spring提供的切面编程,完成每个接口的自动扫描,调用上面的工具类,完成校验。代码如下:

package com.sj.aop.check;import com.sj.enums.ExceptionEnum;
import com.sj.model.common.response.ReturnBean;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;import java.util.Arrays;import static com.sj.util.check.CheckRequiredArgsUtils.check;/*** 基于注解实现入参Model必传字段校验* @author mark* @date 2020/10/06*/
@Slf4j
@Aspect
@Component
@Order(2)
public class CheckRequiredArgsAspect {@Around("execution(public * com.sj.controller..*(..))")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {// 1.校验String checkResult = check(joinPoint);// 2.未通过校验(这里是我项目的处理逻辑,请按照自己的需要覆盖下面代码)if (checkResult != null) {String errMsg = ExceptionEnum.MISSING_REQUIRED_PARAM.getDesc() + checkResult;return ReturnBean.fail(errMsg);}// 3.return joinPoint.proceed();}}

三、使用方法

1. 定义Aop

按照第二章实现细节中的Aop的实现方法,在本模块定义切面,修改扫描位置。至此,该功能已经开始运转了。

2. Model中标识必传字段和嵌套Model

通过@ApiModelProperty注解的required参数标识字段是否必传。通过自定义注解@CheckRequiredArgs标识需要校验的嵌套Model。示例代码如下:

package com.sj.model.house;import com.sj.annotation.check.CheckRequiredArgs;
import com.sj.model.tenant.Tenant;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;/*** @author mark* @date 2020/01/29*/
@Data
public class House4Add {@ApiModelProperty(value = "门牌号", required = true)private String houseNo;@ApiModelProperty(value = "户主姓名", required = true)private String ownerName;@ApiModelProperty(value = "户主手机号")private String ownerPhone;@CheckRequiredArgs@ApiModelProperty(value = "租客", required = true)private Tenant tenant;}

3. Controller层添加自定义注解

@CheckRequiredArgs注解需要加在Controller层方法的入参Model上。使用该注解标识的入参Model,会自动进行必传字段校验。示例代码如下:

@ApiOperation(value = "新增")
@PostMapping
public ReturnBean add(@CheckRequiredArgs @RequestBody House4Add house4Add) {return houseService.add(house4Add);
}

基于自定义注解校验入参Model中的必传字段相关推荐

  1. java 自定义解码_[求助],java如何使用自定义注解对入参进行解密?

    正好之前做过自定义ArgumentResolver相关的开发. 先指出你的一个问题,ArgumentResolver是对你Controller方法上的单个参数进行解析,也就是说,你的参数是什么类型,就 ...

  2. java中注解动态传参_Java自定义注解源码+原理解释(使用Java自定义注解校验bean传入参数合法性)...

    Java自定义注解源码+原理解释(使用Java自定义注解校验bean传入参数合法性)java 前言:因为前段时间忙于写接口,在接口中须要作不少的参数校验,本着简洁.高效的原则,便写了这个小工具供本身使 ...

  3. SpringBoot 整合JWT实现基于自定义注解的-登录请求验证拦截(保姆级教学,附:源码)

    学习目标: Spring Boot 整合JWT实现基于自定义注解的 登录请求接口拦截 例: 一篇掌握 JWT 入门知识  1.1 在学习SpringBoot 整合JWT之前,我们先来说说JWT进行用户 ...

  4. 实战:基于自定义注解实现自定义框架Spring

    实战:基于自定义注解实现自定义框架Spring 一.自定义注解介绍 1.1 通过反射API,可以判断一个类.接口.字段或者方法上是否有注解 Class类(java.lang包下)中提供了一些方法用于反 ...

  5. java校验入参的有效性的工具类

    闲来无事的时候,对之前项目中写过的代码做个简单的总结,其中有一个用于校验入参是否有效的工具类,在此记录一下,以便后续查询使用,现贴出以下代码, public void validateRequestP ...

  6. 自定义注解校验List集合数据

    实现功能:自定义注解实现对信息封装类中的List集合中的元素信息校验,并支持通过注解属性指定是否允许集合为空. 自定义注解 @Target(FIELD) @Retention(RUNTIME) @Co ...

  7. Java校验入参之正则表达式校验特殊字符

    Java校验入参之正则表达式校验特殊字符 前言 实现流程 总结 参考链接 前言 场景描述 第三方调用接口时,有些特殊字符不允许传参,可能影响存储.或者加解密等问题,下面以斜杠"/" ...

  8. spring aop自定义切面打印入参和出参,以及切面获取注解的字段值.

    controller代码 @ApiOperation(value = "检查是否有兑换次数", response = Boolean.class)@ApiImplicitParam ...

  9. SpringBoot系列之使用自定义注解校验用户是否登录

    记得今年年初刚开始面试的时候,被问的最多的就是你知道Spring的两大核心嘛?那你说说什么是AOP,什么是IOC?我相信你可能也被问了很多次了. 1.到底是什么是AOP? 所谓AOP也就是面向切面编程 ...

最新文章

  1. matplotlib多个饼状图
  2. [Leetcode] Copy list with random pointer 对带有任意指针的链表深度拷贝
  3. 深入浅出python机器学习_9.1.5_通过数据预处理提高模型的准确率_MinMaxScaler
  4. 并发编程——进程——进程的同步与数据共享
  5. ASP.NET Core应用程序的参数配置及使用
  6. 多项式对数函数|指数函数(多项式)
  7. 基于JAVA+SpringBoot+Mybatis+MYSQL的酒店管理系统
  8. java、python什么意思_java和python是什么
  9. CSDN的markdown编辑器详细使用说明、语法快速索引手册
  10. 【2.Delphi语法基础】7.程序异常处理
  11. Nature子刊 | 绘制植物叶际菌群互作图谱以建立基因型表型关系
  12. GF(256)下的乘法
  13. 北京市小汽车摇号程序的反编译、算法及存在的问题浅析
  14. 自成一派的风格楷体字体
  15. java 包的位置_通过Java在jar文件所在的位置创建目录
  16. 密码的自动生成器:密码由大写字母/小写字母/数字组成,生成12位随机密码
  17. 从工具了解大数据之Kettle
  18. 使用无线WIFI模块NodeMCU Lua V3物联网开发板8266-01/01S 在Arduino搭建环境到点亮一个LED灯
  19. Java求两个数组的交集、差集、并集
  20. 大数据Flink安装部署

热门文章

  1. QT学习之QMainWindow详解
  2. 教师资格证报名使用IE浏览器
  3. Unity制作头顶血条方式对比与优化
  4. 服务器主机只能读取一个硬盘,服务器pe不识别硬盘(无法读取硬盘原因和解决法)...
  5. 道闸系统服务器功能,道闸系统_停车场自动道闸系统 - 九鼎智能
  6. 融合黄金正弦和曲线自适应的多策略麻雀搜索算法
  7. Qt实现表格控件-支持多级列表头、多级行表头、单元格合并、字体设置等
  8. 【手册】如何编译/修改三星手机Rom(一)
  9. 我的世界java爱暮色森林秋季,Minecraft1.12.2暮色森林暮色恶魂简单攻略
  10. python生成单位矩阵