千家信息网

@ApiImplicitParam对@RequestParam的required属性侵入怎么解决

发表于:2026-11-02 作者:千家信息网编辑
千家信息网最后更新 2026年11月02日,本篇内容主要讲解"@ApiImplicitParam对@RequestParam的required属性侵入怎么解决",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家
千家信息网最后更新 2026年11月02日@ApiImplicitParam对@RequestParam的required属性侵入怎么解决

本篇内容主要讲解"@ApiImplicitParam对@RequestParam的required属性侵入怎么解决",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"@ApiImplicitParam对@RequestParam的required属性侵入怎么解决"吧!

问题起源

使用SpringCloud构建项目时,使用Swagger生成相应的接口文档是推荐的选项,Swagger能够提供页面访问,直接在网页上调试后端系统的接口, 非常方便。最近却遇到了一个有点困惑的问题,演示接口示例如下(原有功能接口带有业务实现逻辑,这里简化了接口):

/**
* @description: 演示类
* @author: Huang Ying
**/
@Api(tags = "演示类")
@RestController
@Slf4j
public class DemoController {

@ApiOperation(value = "测试接口")
@ApiImplicitParams({
@ApiImplicitParam(name = "uid", value = "用户ID", paramType = "query", dataType = "Long")
})
@RequestMapping(value = "/api/json/demo", method = RequestMethod.GET)
public String auth(@RequestParam(value = "uid") Long uid) {
System.out.println(uid);
return "the uid: " + uid;
}
}

问题出在接口参数uid的必填性上,@RequestParam注解里require默认为true,要求必填,但@ApiImplicitParam注解里require默认为false,要求非必填,该业务接口在进行功能联调时,uid居然能得到一个null值,按照一般认知习惯@ApiImplicitParam注解的主要作用是生成接口文档,不应该对@RequestParam的属性有侵入性才对,目前反馈的bug,让我怀疑@ApiImplicitParam是不是会侵入@RequestParam的require属性?

框架选型、版本及主要功能

项目搭建

SpringBoot版本:2.1.6.RELEASESpringCloud版本:Greenwich.SR3

业务模块

SpringCloud业务模块使用的swagger:

swagger bootstrap ui 1.9.6 增强swagger ui样式spring4all-swagger 1.9.0.RELEASE 配置化swagger参数,免去代码开发

业务网关

SpringCloud业务网关使用的swagger:

knife4j 2.0.1 增强swagger ui样式(网关用gateway搭建,swagger使用knife4j-spring-boot-starter依赖,可以聚合业务模块的swagger文档)

此次的范围只针对SpringCloud业务模块,暂时不涉及业务网关的Swagger文档。

测试工具

测试工具目前有两个:swagger doc:使用浏览器进行访问,如下图:

postman:手动配置接口参数,示例:

案例实战

接口测试1

接口示例如开篇所示,我们先使用如下接口,全部使用默认值,即@ApiImplicitParam的required为false,@RequestParam的required为true:

@ApiOperation(value = "测试接口")
@ApiImplicitParams({
@ApiImplicitParam(name = "uid", value = "用户ID", paramType = "query", dataType = "Long")
})
@RequestMapping(value = "/api/json/demo", method = RequestMethod.GET)
public String auth(@RequestParam(value = "uid") Long uid) {
System.out.println(uid);
return "the uid: " + uid;
}

看swagger的结果:

看postman的结果:

接口测试2

我们修改@ApiImplicitParam的required值为true,@RequestParam不变,重启模块@ApiImplicitParam(name = "uid", value = "用户ID", paramType = "query", required = true, dataType = "Long")

看swagger的结果:

通过调试浏览器可以发现,为空校验是js完成的,js判断为空后,并未发起请求到后端,这样我们可以认为swagger内@ApiImplicitParam的required参数生效了。

接口测试3

在前面我们使用postman测试接口时,发现参数项是空的,我们加上参数,但不写值测试后,结果让人诧异:

并且无论@ApiImplicitParam的required值如何修改,结果都是一样的,肯定有一个地方是搞错了,导致我们误判。

后来仔细查阅资料,发现是我们对@RequestParam的required参数理解错了,这个required为true的含义是:接口参数名一定要存在,但参数后面有没有值它管不着。拿刚刚的例子来说:

这两个请求是通过的:
localhost:8080/api/json//demo?uid
localhost:8080/api/json//demo?uid=

只有这种请求是不通过的:
localhost:8080/api/json//demo?
小结论

经过上述三个接口的测试场景,我们至少可以明确3点:

  1. @ApiImplicitParam的required参数不会对@RequestParam的required值造成侵入,它们俩不相关。

  2. @ApiImplicitParam的required参数会影响swagger doc的js逻辑判断,为空校验是在js层面上完成的。

  3. @RequestParam的required参数默认情况下只会校验是否有该参数名,不校验它是否有值。

源码剖析

swagger部分

上一节当中提及swagger读取@ApiImplicitParam注解的required参数,最终会体现在js上,通过浏览器F12的追踪,定位到swaggerbootstrapui.js文件上,这里摘抄部分源码:

# 点击发送按钮时,逐行读取参数信息,并提取required参数
paramBody.find("tr").each(function () {
var paramtr=$(this);
var cked=paramtr.find("td:first").find(":checked").prop("checked");
var _urlAppendflag=true;
//that.log(cked)
if (cked){
//如果选中,留意此行的required:paramtr.data("required")信息提取
var trdata={name:paramtr.find("td:eq(2)").find("input").val(),in:paramtr.data("in"),required:paramtr.data("required"),type:paramtr.data("type"),emflag:paramtr.data("emflag"),schemavalue:paramtr.data("schemavalue")};
//that.log("trdata....")
//that.log(trdata);
//获取key
//var key=paramtr.find("td:eq(1)").find("input").val();
var key=trdata["name"];
//获取value
var value="";
var reqflag=false;
// 后面代码省略
}
})

js上判断该属性required是否为true的处理,js源码如下:

//判断是否required
if (trdata.hasOwnProperty("required")){
var required=trdata["required"];
if (required){
if(!reqflag){
//必须,验证value是否为空
if(value==null||value==""){
validateflag=true;
var des=trdata["name"]
//validateobj={message:des+"不能为空"};
validateobj={message:des+i18n.message.debug.fieldNotEmpty};
return false;
}
}
}

}
SpringCloud业务模块部分

swagger前端js验证通过可以向后台发送请求,或者使用postman向后台系统发送请求时,开始进入后台的一系列过滤器、Servlet处理,东西还不少:

// 实际的业务方法部分
auth:28, DemoController (com.hy.demo.controller)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)

// 请求参数的提取、控制部分
doInvoke:190, InvocableHandlerMethod (org.springframework.web.method.support)
invokeForRequest:138, InvocableHandlerMethod (org.springframework.web.method.support)
invokeAndHandle:104, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)
invokeHandlerMethod:892, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handleInternal:797, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handle:87, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)

// 下面是各种基础Web服务组件的过滤器等,暂时不关心
doDispatch:1039, DispatcherServlet (org.springframework.web.servlet)
doService:942, DispatcherServlet (org.springframework.web.servlet)
processRequest:1005, FrameworkServlet (org.springframework.web.servlet)
doGet:897, FrameworkServlet (org.springframework.web.servlet)
service:634, HttpServlet (javax.servlet.http)
service:882, FrameworkServlet (org.springframework.web.servlet)
service:741, HttpServlet (javax.servlet.http)
internalDoFilter:231, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilter:84, SecurityBasicAuthFilter (com.github.xiaoymin.swaggerbootstrapui.filter)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, ProductionSecurityFilter (com.github.xiaoymin.swaggerbootstrapui.filter)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilter:124, WebStatFilter (com.alibaba.druid.support.http)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:88, HttpTraceFilter (org.springframework.boot.actuate.web.trace.servlet)
doFilter:109, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:99, RequestContextFilter (org.springframework.web.filter)
doFilter:109, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:92, FormContentFilter (org.springframework.web.filter)
doFilter:109, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:93, HiddenHttpMethodFilter (org.springframework.web.filter)
doFilter:109, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
filterAndRecordMetrics:114, WebMvcMetricsFilter (org.springframework.boot.actuate.metrics.web.servlet)
doFilterInternal:104, WebMvcMetricsFilter (org.springframework.boot.actuate.metrics.web.servlet)
doFilter:109, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:200, CharacterEncodingFilter (org.springframework.web.filter)
doFilter:109, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
invoke:202, StandardWrapperValve (org.apache.catalina.core)
invoke:96, StandardContextValve (org.apache.catalina.core)
invoke:490, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:139, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:74, StandardEngineValve (org.apache.catalina.core)
service:343, CoyoteAdapter (org.apache.catalina.connector)
service:408, Http11Processor (org.apache.coyote.http11)
process:66, AbstractProcessorLight (org.apache.coyote)
process:853, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1587, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)

聚集重点在请求参数的读取校验方面,首先看org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver类的resolveArgument方法:

@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

// 留意此方法调用
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();

Object resolvedName = resolveStringValue(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
// 后面暂时省略
}

getNamedValueInfo方法的实现如下:

/**
* Obtain the named value for the given method parameter.
*/
private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
if (namedValueInfo == null) {
namedValueInfo = createNamedValueInfo(parameter);
namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
this.namedValueInfoCache.put(parameter, namedValueInfo);
}
return namedValueInfo;
}

进入createNamedValueInfo(parameter)方法时,这部分代码如下:

@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
}

/**
* NamedValueInfo的定义
* Represents the information about a named value, including name, whether it's required and a default value.
*/
protected static class NamedValueInfo {

private final String name;

private final boolean required;

@Nullable
private final String defaultValue;

public NamedValueInfo(String name, boolean required, @Nullable String defaultValue) {
this.name = name;
this.required = required;
this.defaultValue = defaultValue;
}
}

这段代码很关键,这里只读取@RequestParam注解,不会读@ApiImplicitParam注解,所以@ApiImplicitParam注解不会影响@RequestParam的属性,并且无论是从swagger doc过来的请求,还是postman过来的请求,都执行这一段代码,最终读取注解的结果用CurrenctHashMap存储,key的格式是method 'xxx' parameter y,xxx为方法名,y为参数的顺序号,如method 'auth' parameter 0,基本上可以保证唯一性。

阶段性总结

源码阅读到这里,基本上可以验证前面提及的小结论的前2条,引用一下:

  1. @ApiImplicitParam的required参数不会对@RequestParam的required值造成侵入,它们俩不相关。

  2. @ApiImplicitParam的required参数会影响swagger doc的js逻辑判断,为空校验是在js层面上完成的。

  3. @RequestParam的required参数默认情况下只会校验是否有该参数名,不校验它是否有值。

前面2个问题已经从源码中找到解释,来看第3个问题:如果参数设置required=true,但只是要求参数名存在,如果此字段是Long类型或Integer类型,写成uid=或'uid',也能通过校验,最终进入方法后,还是得手动写代码进行为空校验,这显然不是我们想要的结果?该如何解决呢?

请求参数data bind的问题

接上一节,如果这样通用的参数,得挨个判断是否为空,这样的做法就有点难受了,有没有更好的解决办法呢?预期的实现效果是字段加上require=true后,Long类型或其他数值类型可以把"",null过滤掉,要不然require还有什么意义呢?

解决方法有两个思路:

  1. POST请求方法中将多个参数封装到一个POJO类里,用@RequestBody声明,POJO类中可以使用@Validator框架的@NotNull等注解,并在参数前声明@Valid。

  2. 自定义参数绑定规则扩展。

方案2更通用一些,适用GET、POST请求,并且原有的单个参数声明无需封装到POJO类里。

官网本身提供自定义参数绑定的扩展,见https://docs.spring.io/spring/docs/5.1.8.RELEASE/spring-framework-reference/web.html#mvc-ann-initbinder

官网的例子是在指定的Controller类中使用@InitBinder注解,影响范围仅限该Controller类,示例如下:

@InitBinder
public void initBinder(WebDataBinder binder) {
/*
* 注册对于String类型参数对象的属性进行trim操作的编辑器,
* 构造参数代表空串是否转为null,false,则将null转为空串。
*/
binder.registerCustomEditor(String.class, new StringTrimmerEditor(false));
// 这里我还添加了其他类型的属性编辑器,true表示允许使用"",并且将""处理为空,false表示不允许使用""
binder.registerCustomEditor(Short.class, new CustomNumberEditor(Short.class, false));
binder.registerCustomEditor(Integer.class, new CustomNumberEditor(Integer.class, false));
binder.registerCustomEditor(Long.class, new CustomNumberEditor(Long.class, false));
binder.registerCustomEditor(Float.class, new CustomNumberEditor(Float.class, false));
binder.registerCustomEditor(Double.class, new CustomNumberEditor(Double.class, false));
binder.registerCustomEditor(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, false));
binder.registerCustomEditor(BigInteger.class, new CustomNumberEditor(BigInteger.class, false));
}

由于此次面临的问题是全模块@RequestParam的值的问题,需要做一个全局的配置,此时需要新增一个类,并使用@ControllerAdvice注解,代码如下:

@ControllerAdvice
public class CustomWebBindingInitializer implements WebBindingInitializer {

@InitBinder
@Override
public void initBinder(WebDataBinder binder) {
/*
* 注册对于String类型参数对象的属性进行trim操作的编辑器,
* 构造参数代表空串是否转为null,false,则将null转为空串。
*/
binder.registerCustomEditor(String.class, new StringTrimmerEditor(false));
// 这里我还添加了其他类型的属性编辑器,true表示允许使用"",并且将""处理为空,false表示不允许使用""
binder.registerCustomEditor(Short.class, new CustomNumberEditor(Short.class, false));
binder.registerCustomEditor(Integer.class, new CustomNumberEditor(Integer.class, false));
binder.registerCustomEditor(Long.class, new CustomNumberEditor(Long.class, false));
binder.registerCustomEditor(Float.class, new CustomNumberEditor(Float.class, false));
binder.registerCustomEditor(Double.class, new CustomNumberEditor(Double.class, false));
binder.registerCustomEditor(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, false));
binder.registerCustomEditor(BigInteger.class, new CustomNumberEditor(BigInteger.class, false));
}

}

注意一下CustomNumberEditor实例初始化的传的false参数。

重启应用,看一下效果:

扩展DataBinder后相关源码阅读

都已经到这儿了,再加把劲把相关的源码看一下,还是在org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver类的resolveArgument方法的后半段:

@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 前面省略
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
// 在这里对参数进行转换
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
catch (ConversionNotSupportedException ex) {
throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
catch (TypeMismatchException ex) {
throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());

}
}

handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

return arg;
}

从binder.convertIfNecessary方法一路跟下去,中间省略一些调用,最终到达org.springframework.beans.propertyeditors.CustomNumberEditor类的setAsText方法:

/**
* Parse the Number from the given text, using the specified NumberFormat.
*/
@Override
public void setAsText(String text) throws IllegalArgumentException {
if (this.allowEmpty && !StringUtils.hasText(text)) {
// Treat empty String as null value.
setValue(null);
}
else if (this.numberFormat != null) {
// Use given NumberFormat for parsing text.
setValue(NumberUtils.parseNumber(text, this.numberClass, this.numberFormat));
}
else {
// Use default valueOf methods for parsing text.
setValue(NumberUtils.parseNumber(text, this.numberClass));
}
}

仔细看allowEmpty变量,针对Long类型的参数,我们扩展数据绑定时,该变量设置的是false,表示不接受空值,试验中我们传的值是空串,那么这里的条件分支判断就必须对空串转换成数值,执行Long.valueOf("")结果报出运行时异常java.lang.NumberFormatException,告知客户端参数不对,这是期望的结果。

到此,相信大家对"@ApiImplicitParam对@RequestParam的required属性侵入怎么解决"有了更深的了解,不妨来实际操作一番吧!这里是网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

参数 接口 方法 属性 业务 注解 测试 类型 结果 问题 代码 模块 源码 空串 部分 文档 示例 编辑器 网关 处理 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 伟宏互联网科技 江苏根荣互联网科技 互联网是不是新科技 互联网为科技 互联网科技公司发展 鑫亿盛互联网科技 科技互联网创业计划书 互联网公司会议桌创意科技软装饰 深圳国际航运科技和互联网 科技互联网的估值 互联网大会黑科技排名 互联网科技有哪些公司 健康食品互联网创新科技园项目 国大互联网教育科技有限公司工作 中国科技创新与世界互联网大会 山东子淼互联网科技有限公司 互联网企业科技类企业如何做账 21世纪互联网科技成就 江苏互联网智能科技产品出厂价 科技发展第三阶段互联网时代 科技互联网 互联网黑科技 哈尔滨大商圈互联网科技有限公司 广州互联网科技公司 互联网金融公司科技 唐信互联网科技 成都仁贤领冠互联网科技有限公司 荆州市昕露互联网络科技有限公司 互联网科技和信息 山东星卡互联网科技有限公司官网
0