Return HTTP 状态 BAD_REQUEST 在 Spring 中使用自定义解析器

Return HTTP Status BAD_REQUEST using a custom resolver in Spring

我正在使用 Spring Boot 1.5.15 开发 REST API。我实现了一个客户 HandlerMethodArgumentResolver 来映射一个 HTTP header。具体来说,我给HTTP赋值headerSome-Header,去掉前缀"XXX ".

首先,我定义了一个自定义注解。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface SomeHeader {
}

然后,我实现了一个自定义解析器。

public class SomeHeaderArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterAnnotation(SomeHeader.class) != null;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter,
                                  ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest,
                                  WebDataBinderFactory binderFactory) {
        HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
        final String headerValue = request.getHeader("Some-Header");

        return headerValue.replace("XXX ", "");
    }
}

最后,我让 Spring 知道配置 class 中的解析器。

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new SomeHeaderArgumentResolver());
    }
}

现在,我可以在我需要的任何控制器中使用以下映射。

@PostMapping("/some/paath")
public void someMethod(@SomeHeader String someHeaderValue) {
   // Method body...
}

但是,Some-Header 信息对我来说是强制性的。如果它不存在,我希望 Spring returns 对调用者的 400 Bad Request 响应。这与我使用 @RequestHeader("Some-Header") 注释可以获得的行为相同。

我可以复制相同的行为吗?可能,我不想使用专用的 controller advice.

您可以为任何情况声明自己的异常并将控制器中的 ExceptionHandler 设置为 return 正确 http-status。

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler({SameHeaderException.class})
public Object onSameHeaderException(SameHeaderException e) {
    return ImmutableMap.of("reason", e.getMessage());
}

所以如果 header 不存在,你可以抛出这个异常:

if (someHeaderValue == null) { throw new SameHeaderException(); }

如果您看到用于 @RequestHeaderRequestHeaderMethodArgumentResolver 的实现,您将看到 AbstractNamedValueMethodArgumentResolver 抽象 [=33] 的 handleMissingValue 方法的重写实现=] 如下:

@Override
protected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException {
        throw new ServletRequestBindingException("Missing request header '" + name +
                "' for method parameter of type " + parameter.getNestedParameterType().getSimpleName());
}

handleMissingValue 方法用于AbstractNamedValueMethodArgumentResolverresolveArgument 方法,RequestHeaderMethodArgumentResolver 根据某些条件对其进行扩展。因此,当 header 不存在并且抛出 ServletRequestBindingException 时,Spring 的 DefaultHandlerExceptionResolver 会处理并发送 400 响应.

这就是 @RequestHeader 情况下验证的工作方式。因此,您可以在 SomeHeaderArgumentResolver class 的 resolveArgument 方法中实现类似的内容,如下所示:

public class SomeHeaderArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterAnnotation(SomeHeader.class) != null;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter,
                                  ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest,
                                  WebDataBinderFactory binderFactory) {
        HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
        final String headerValue = request.getHeader("Some-Header");
        if(headerValue != null){
           return headerValue.replace("XXX ", "");
        } else {
           //handle scenario if header absent with ServletRequestBindingException
        }
       }
    }

感谢@madhu-bhat 的建议,我明白了 class 扩展的正确方法是让 Spring 发挥作用。

class RequestHeaderMethodArgumentResolver,被 Spring 用于解析 Java object 中的 HTTP headers 值,扩展了抽象 class AbstractNamedValueMethodArgumentResolver。 class 允许您指定 header 值是否有一些默认值,使用 createNamedValueInfo 方法。

所以,它遵循代码。

public class SomeHeaderArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
    @Override
    protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
        // The second parameter specifies if the value is required, 
        // and the third if there is some default value.
        return new NamedValueInfo("", true, null);
    }

    @Override
    protected Object resolveName(String name, 
                                 MethodParameter parameter, 
                                 NativeWebRequest request) {
        final String headerValue = request.getHeader("Some-Value");
        if (StringUtils.isEmpty(headerValue)) {
            // Returning null tells Spring that there is no value for the parameter
            return null;
        }
        return headerValue.replace("XXX ", "");
    }

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return (parameter.hasParameterAnnotation(SomeHeader.class) &&
                !Map.class.isAssignableFrom(
                    parameter.nestedIfOptional().getNestedParameterType()));
    }
}

我唯一不喜欢的是我正在使用为处理命名值而开发的结构,但我没有命名值.

希望对您有所帮助。