【问题标题】:Set query param value to default on type mismatch in spring boot rest api在spring boot rest api中将查询参数值设置为默认类型不匹配
【发布时间】:2022-10-24 06:32:12
【问题描述】:

我们有一个用 spring boot 和 openapi 代码生成器构建的 rest api。假设我们的 api 规范中有一条路径 /user

...
"paths": {
    "/user": {
        "get": {
            "parameters": [{
                "name": "id",
                "in": "query",
                "required": false,
                "type": "integer",
                "format": "int64"
            }],
            "responses": { ... }
        },
    }
    // more paths
}
...

对该路径的调用可能是:/user?id=1234

代码生成器创建一个接口 MyControllerApi:

@javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "some date")
@Api(value="user")
public interface MyControllerApi {
    @ApiOperation(value="", nickname="userGet", response = User.class, /* ... */)
    @ApiResponse(/* ... */)
    @GetMapping(value="/user", produces = { "application/json" })
    ResponseEntity<User> userGet(@ApiParam(value = "id") @RequestParam(value = "id", required = false) Long id);
}    

然后控制器看起来像这样:

@RestController
public class MyController implements MyControllerApi
{
    @Autowired
    UserService service;
    
    @Override
    public ResponseEntity<User> userGet(@RequestParam(value = "id") Long id) {
        return service.get(id);
    }
}

如果请求/user?id=&lt;value&gt;,spring boot会自动检查传入的参数值&lt;value&gt;的类型是否与需要的类型匹配。如果不是 BAD_REQUEST 则返回:

{
    "timestamp": "2022-10-19T17:20:48.393+0000",
    "status": 400,
    "error": "Bad Request",
    "path": "/user"
}

我们现在处于想要将null 传递给userGet 的每个参数的情况下,这会导致类型不匹配。更清楚一点:如果请求/user?id=abcd,则应调用userGet,并将id设置为null,以便我们可以返回一些默认用户。有没有办法做到这一点?

当然还有更多的路径和参数,这种行为应该适用于LongBoolean 类型的每个查询参数。

这个例子可能没有多大意义,但它也只是一个例子。

提前致谢

在此期间我自己尝试了什么

1.设置spring-bootuseOptional选项...

... 到 pom.xml 中的 true(参见 here)。

这会影响控制器方法中的查询参数是Optional&lt;?&gt; 类型。在我上面的示例中,这将导致:

@Override
public ResponseEntity<User> userGet(Optional<Long> id) {
    Long id_val = id.isPresent() ? id.get() : null;
    return service.get(id_val);
}

但这也不起作用,spring boot 还会对参数类型不匹配创建BAD_REQUEST 响应。

2.使用请求拦截器

请求拦截器是一种中间件,可以通过实现HandlerInterceptor 创建,使您能够在将请求传递给控制器​​之前对其进行处理。

我的拦截器如下所示:

public class CustomRequestInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
    {
        HandlerMethod method = (HandlerMethod) handler;
        MethodParameter[] handlerParams = method.getMethodParameters();

        while (request.getParameterNames().hasMoreElements())
        {
            // the param name of the query
            String paramName = request.getParameterNames().nextElement();
             the param value
            String paramValue = request.getParameter(paramName);
            MethodParameter methodParam = null;

            for (MethodParameter mp : handlerParams)
            {
                // We can access annotations here ...
                Annotation anno = mp.getParameterAnnotation(RequestParam.class);
                if(anno != null)
                {
                    RequestParam rp = (RequestParam) anno;

                    // ... and am able to get the param name in the controller
                    // Check if we found the param
                    String requestParamName = rp.value();
                    if (requestParamName.toLowerCase().equals(paramName.toLowerCase()))
                    {
                        // and now?
                        break;
                    }
                }
            }

            if (methodParam != null)
            {
                Type type = methodParam.getGenericParameterType();
            }
        }

        return HandlerInterceptor.super.preHandle(request, response, handler);
    }
}

到目前为止一切都很好,但是 RequestParam-Object 不包含有关参数类型的任何信息,也不包含方法参数列表中该参数的索引。并且 MethodParameter 不包含参数的名称(因为我认为它是一个已编译的类)。

我想知道的是spring boot本身如何将查询参数映射到控制器方法中的参数.....?

【问题讨论】:

    标签: java spring-boot openapi-generator


    【解决方案1】:

    在做了一些研究后,我找到了使用自定义HandlerMethodArgumentResolver 的解决方案:

    public class MyCustomResolver implements HandlerMethodArgumentResolver {
        @Override
        @Nullable
        public Object resolveArgument(
            MethodParameter methodParam,
            @Nullable ModelAndViewContainer mvContainer,
            NativeWebRequest request,
            @Nullable WebDataBinderFactory binderFac
        ) throws Exception {
            String paramValue = request.getParameter(methodParam.getParameterName());
            if (paramValue == null)
                return null;
    
            if (methodParam.getParameterType().equals(Long.class)) {
                try {
                    // Set param value to the parsed one, if it can be parsed
                    return Long.parseLong(paramValue);
                } catch (Exception e) {
                    // Set param value to null otherwise
                    return null;
                }
            }
    
            return null;
        }
    
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return parameter.getParameterType().equals(Long.class));
        }
    }
    

    方法supportsParameter() 让您有机会决定是否将在resolveArgument 中处理传递的参数。

    resolveArgument 然后获取参数为MethodParameter。与HandlerInterceptor 不同,MethodParameter 现在包含参数名称和类型。

    使用这种方法id 将具有以下值:

    id = 1     on /user?id=1
    id = null  on /user?id=abc
    

    现在我可以直接在控制器中处理虚假的参数值。

    评论

    如果您使用Boolean 类型的参数,则除true 之外的每个参数值的参数值都不会为空,而是“假”。只有在请求resolveArgument 中传递了“true”时,才会将其解析为true,而getUser 将收到true

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-11-17
      • 2021-12-24
      • 1970-01-01
      • 2018-11-14
      • 2017-03-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多