【问题标题】:Custom response on bad request using spring RestController使用spring RestController对错误请求的自定义响应
【发布时间】:2015-06-29 17:41:55
【问题描述】:

我有以下控制器。我正在使用 Spring 创建 Restful API。

@RestController
public class UserController extends RestControlValidator {

@RequestMapping(value = "/user/", method = RequestMethod.POST, headers = "Accept=application/json", consumes = "application/json", produces = MediaType.APPLICATION_JSON_VALUE)
public @ResponseBody List newUser(@RequestBody @Valid UserInput input,BindingResult result) 
            {Some code}

}

UserInput 类如下所示:

public class UserInput{

    @NotEmpty
    private String emailId;
    @NotEmpty
    private String fName;
    private String lName;
    private int sex;

//getters and setters

现在,当我尝试使用数据 {"sex":"Male"} 访问 /user/ 时,我得到以下响应:

我希望此类请求的响应是:

{"errors":{"sex":"The value must be an integer"}}

有没有办法在 Spring 中自定义 BAD REQUEST 响应?

【问题讨论】:

  • @Mudassar 它的目的是作为一个发布请求。我通过 POST 访问它。响应的状态码符合预期。我只是想更改我收到的错误请求的响应
  • 对不起哥们,我意识到晚了,所以立即删除了
  • 您正在寻找的验证是仅适用于性别变量还是适用于所有变量类型。
  • @Mudassar 不只是一个变量。我希望概括 400 响应代码的响应。根据提出 BAD REQUEST 的字段,我希望修改 API 提供的响应。例如回复可能是{"errors":{"fName":"This field cannot be empty"}}

标签: java spring rest spring-mvc


【解决方案1】:

考虑到当前场景,最理想的解决方案是改变 HandlerMethodArgumentResolve 的行为,因为 @RequestBody 构造的 json 到 pojo 失败,因为我们没有机会检查错误的数据,而且这个检查可以很好地在自定义消息转换器

A.首先我们需要按如下方式创建 LanguageMes​​sageConverter

public class LanguageMessageConverter extends
        AbstractHttpMessageConverter<Language> {
    private Gson gson = new Gson();
public LanguageMessageConverter() {
    super(new MediaType("application", "json", Charset.forName("UTF-8")));
}

@Override
protected boolean supports(Class<?> clazz) {
    return Language.class.equals(clazz);
}

Map<String, String> mp = new HashMap<>();

@Override
protected Language readInternal(Class<? extends Language> clazz,
        HttpInputMessage httpInputMessage) throws IOException,
        HttpMessageNotReadableException {
    Map langmp = gson.fromJson(
            convertStreamToString(httpInputMessage.getBody()), Map.class);

        for (Field field : clazz.getDeclaredFields()) {

            if (!langmp.get(field.getName()).getClass().getCanonicalName().equals(field.getType().getCanonicalName())) {
                if (field.getType().getCanonicalName().equals("java.lang.Integer")||field.getType().getCanonicalName().toString().equals("int")) {
                    langmp.put(field.getName(), "0");
                } else if (field.getType().equals("java.lang.String")) {
//TODO COde needs to be improved here because this check is not efficient
                    langmp.put(field.getName(), "wrong");
                }
            }
        }       
        Language lang = gson.fromJson(gson.toJson(langmp), clazz);
        return lang;
    }
  1. 我们需要设置媒体类型 new MediaType("application", "json", Charset.forName("UTF-8")) 以确保此类干预提到的 MIME 类型
  2. 考虑到我们需要处理结果,我发现最好将其转换为映射 langmp(可以使用更好的 JSON 解析器)
  3. 由于我们需要了解现有类型,我使用反射 api 通过 getDeclaredFields() 获取字段
  4. 使用上述方法对数据类型进行逻辑检查,以了解类型是否不正确,例如字段数据类型是否为 int,如果发现为 String,则将替换相应的映射值
  5. 一旦完成,地图将保存更新的值,如果数据错误,则会设置默认值,例如,如果 int var 设置为 0,因为原始 json 中有一个字符串。
  6. 完成后,更新后的地图将转换为相关类。

B.其次,我们需要在 dispatcher xml 中注册自定义 MessageConverter,即LanguageMessageConverter

<mvc:annotation-driven >
   <mvc:message-converters register-defaults="true">       
       <bean class="com.comp.org.controller.LanguageMessageConverter" />
    </mvc:message-converters>
</mvc:annotation-driven>
  1. register-defaults="true" 非常重要,因为我们正在添加自定义 MessageConverter,但我们还需要其他现有转换器与我们添加的转换器一起工作
  2. LanguageMes​​sageConverter 需要在这里注册。

C.考虑到相关的 pojo 填充了必要的细节,它将在自定义转换器中到达我们的控制器后处理,现在我们将添加手动验证,例如。如果 int 变量为 0,则应返回必要的错误 json

根据您的要求,即使 json 包含错误的数据,自定义消息转换器也应该对其进行处理,因此我们可以在控制器中验证所提到的条件。 代码肯定可以进一步改进。请让我知道此解决方案是否满足您的要求或代码的任何部分需要进一步阐述,并希望能解决您的问题。

【讨论】:

  • 如果 Spring 以 400 响应,您如何访问 BindingResult(他们已经拥有)?
  • 我已经回复了如何构造预期错误 json 的部分解决方案。如果有任何部分不正确或没有为解决方案增加价值,请告诉我。
  • OP 在他们的处理方法中已经有了BindingResult。我们可以假设他们计划使用它。如果 Spring 以 400 响应,它永远不会调用处理程序方法,那么 OP 将在哪里执行您的建议?
  • @Mudassar 问题是流量永远不会到达if (result.hasErrors())。我尝试在调试模式下运行它,但在执行控制器代码之前返回响应。
  • 感谢您提供澄清要求的意见。请检查更新的解决方案,它应该可以解决您的问题。
【解决方案2】:

我遇到了同样的问题,但我用这种方式解决了:

  1. 像这样创建一个名为Error的对象(不要忘记实现Serializable...):

    private String fieldName;
    private String errorCode;
    private String defaultMessage;
    
    public Error() {
    }
    
    public Error(String fieldName, String errorCode, String defaultMessage) {
        this.fieldName = fieldName;
        this.errorCode = errorCode;
        this.defaultMessage = defaultMessage;
    }
    
    /* getters, setters */
    
  2. @RestController 方法中,您必须调用 inputValidator.validate() 方法(如果您没有为您的 UserInput 创建对象验证器,那么我们真的说的不是同一种语言......)

    // validating the userInput
    userInputValidator.validate(userInput, bindingResult);
    
    if (bindingResult.hasErrors()) {
        List<Error> errors = new ArrayList<>(bindingResult.getErrorCount());
        for (FieldError fieldWithError : bindingResult.getFieldErrors()) {
            errors.add(new Error(fieldWithError.getField(), fieldWithError.getCode(), fieldWithError.getDefaultMessage()));
        }
        return errors;
    }
    
    // in case of success:
    return null;
    
  3. 最后,您必须将 JSON 对象翻译到您的客户端。您将拥有两种对象:

    3.1。 null(undefined 取决于您使用的语言)

    3.2。像这样的 JSON 对象:

    [
      {
        "fieldName": "name",
        "errorCode": "user.input.name.in.blank",
        "defaultMessage": "Insert a valid name!"
      },
      {
        "fieldName": "firstPhone",
        "errorCode": "user.input.first.phone.blank",
        "defaultMessage": "Insert a valid first phone!"
      }
    ]
    

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-01-13
    • 2020-07-25
    • 2014-12-01
    • 2015-09-04
    • 1970-01-01
    • 2014-04-16
    • 2020-01-19
    • 2019-01-01
    相关资源
    最近更新 更多