基于自定义注解校验入参Model中的必传字段
文章目录
- 一、概述
- 二、实现细节
- 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中的必传字段相关推荐
- java 自定义解码_[求助],java如何使用自定义注解对入参进行解密?
正好之前做过自定义ArgumentResolver相关的开发. 先指出你的一个问题,ArgumentResolver是对你Controller方法上的单个参数进行解析,也就是说,你的参数是什么类型,就 ...
- java中注解动态传参_Java自定义注解源码+原理解释(使用Java自定义注解校验bean传入参数合法性)...
Java自定义注解源码+原理解释(使用Java自定义注解校验bean传入参数合法性)java 前言:因为前段时间忙于写接口,在接口中须要作不少的参数校验,本着简洁.高效的原则,便写了这个小工具供本身使 ...
- SpringBoot 整合JWT实现基于自定义注解的-登录请求验证拦截(保姆级教学,附:源码)
学习目标: Spring Boot 整合JWT实现基于自定义注解的 登录请求接口拦截 例: 一篇掌握 JWT 入门知识 1.1 在学习SpringBoot 整合JWT之前,我们先来说说JWT进行用户 ...
- 实战:基于自定义注解实现自定义框架Spring
实战:基于自定义注解实现自定义框架Spring 一.自定义注解介绍 1.1 通过反射API,可以判断一个类.接口.字段或者方法上是否有注解 Class类(java.lang包下)中提供了一些方法用于反 ...
- java校验入参的有效性的工具类
闲来无事的时候,对之前项目中写过的代码做个简单的总结,其中有一个用于校验入参是否有效的工具类,在此记录一下,以便后续查询使用,现贴出以下代码, public void validateRequestP ...
- 自定义注解校验List集合数据
实现功能:自定义注解实现对信息封装类中的List集合中的元素信息校验,并支持通过注解属性指定是否允许集合为空. 自定义注解 @Target(FIELD) @Retention(RUNTIME) @Co ...
- Java校验入参之正则表达式校验特殊字符
Java校验入参之正则表达式校验特殊字符 前言 实现流程 总结 参考链接 前言 场景描述 第三方调用接口时,有些特殊字符不允许传参,可能影响存储.或者加解密等问题,下面以斜杠"/" ...
- spring aop自定义切面打印入参和出参,以及切面获取注解的字段值.
controller代码 @ApiOperation(value = "检查是否有兑换次数", response = Boolean.class)@ApiImplicitParam ...
- SpringBoot系列之使用自定义注解校验用户是否登录
记得今年年初刚开始面试的时候,被问的最多的就是你知道Spring的两大核心嘛?那你说说什么是AOP,什么是IOC?我相信你可能也被问了很多次了. 1.到底是什么是AOP? 所谓AOP也就是面向切面编程 ...
最新文章
- matplotlib多个饼状图
- [Leetcode] Copy list with random pointer 对带有任意指针的链表深度拷贝
- 深入浅出python机器学习_9.1.5_通过数据预处理提高模型的准确率_MinMaxScaler
- 并发编程——进程——进程的同步与数据共享
- ASP.NET Core应用程序的参数配置及使用
- 多项式对数函数|指数函数(多项式)
- 基于JAVA+SpringBoot+Mybatis+MYSQL的酒店管理系统
- java、python什么意思_java和python是什么
- CSDN的markdown编辑器详细使用说明、语法快速索引手册
- 【2.Delphi语法基础】7.程序异常处理
- Nature子刊 | 绘制植物叶际菌群互作图谱以建立基因型表型关系
- GF(256)下的乘法
- 北京市小汽车摇号程序的反编译、算法及存在的问题浅析
- 自成一派的风格楷体字体
- java 包的位置_通过Java在jar文件所在的位置创建目录
- 密码的自动生成器:密码由大写字母/小写字母/数字组成,生成12位随机密码
- 从工具了解大数据之Kettle
- 使用无线WIFI模块NodeMCU Lua V3物联网开发板8266-01/01S 在Arduino搭建环境到点亮一个LED灯
- Java求两个数组的交集、差集、并集
- 大数据Flink安装部署
热门文章
- QT学习之QMainWindow详解
- 教师资格证报名使用IE浏览器
- Unity制作头顶血条方式对比与优化
- 服务器主机只能读取一个硬盘,服务器pe不识别硬盘(无法读取硬盘原因和解决法)...
- 道闸系统服务器功能,道闸系统_停车场自动道闸系统 - 九鼎智能
- 融合黄金正弦和曲线自适应的多策略麻雀搜索算法
- Qt实现表格控件-支持多级列表头、多级行表头、单元格合并、字体设置等
- 【手册】如何编译/修改三星手机Rom(一)
- 我的世界java爱暮色森林秋季,Minecraft1.12.2暮色森林暮色恶魂简单攻略
- python生成单位矩阵