SpringResponseStatusException 不返回原因

我有一个非常简单的 @RestController,我试图设置一个自定义错误消息。但由于某些原因,该错误的 message没有显示出来。

这是我的控制器:

@RestController
@RequestMapping("openPharmacy")
public class OpenPharmacyController {




@PostMapping
public String findNumberOfSurgeries(@RequestBody String skuLockRequest) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "This postcode is not valid");
}


}

我得到的回答是:

{
"timestamp": "2020-06-24T17:44:20.194+00:00",
"status": 400,
"error": "Bad Request",
"message": "",
"path": "/openPharmacy/"
}

我正在传递一个 JSON,但我没有验证任何东西,我只是试图设置自定义消息。如果我更改状态代码,我会在响应中看到它,但是 message始终是空的。

为什么不能像预期的那样工作?这是一个如此简单的例子,我看不出可能遗漏了什么。在调试代码时,我可以看到错误消息设置了所有字段。但由于某些原因,消息从未在响应上设置。

36193 次浏览

我也有同样的问题,如果我使用这个结构

throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Error in update");

我的消息不是通过 JSON传递给客户端的。对我来说,解决这个问题的唯一方法就是创建 GlobalExceptionHandler

package mypackage;


import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import java.util.Date;


@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NotFoundException.class)
public ResponseEntity<ErrorDTO> generateNotFoundException(NotFoundException ex) {
ErrorDTO errorDTO = new ErrorDTO();
errorDTO.setMessage(ex.getMessage());
errorDTO.setStatus(String.valueOf(ex.getStatus().value()));
errorDTO.setTime(new Date().toString());


return new ResponseEntity<ErrorDTO>(errorDTO, ex.getStatus());
}
}

我还创建了自己的 Exception类型

package mypackage;


import org.springframework.http.HttpStatus;


public class NotFoundException extends RuntimeException {


public NotFoundException(String message) {
super(message);
}


public HttpStatus getStatus() {
return HttpStatus.NOT_FOUND;
}
}

有了这个,我就能够从控制器抛出异常,并且在 JSON中得到正确的结果-我想看到的消息。

@PutMapping("/data/{id}")
public DataEntity updateData(@RequestBody DataEntity data, @PathVariable int id) {
throw new NotFoundException("Element not found");
}

我还得介绍 ErrorDTO

package mypackage;


public class ErrorDTO {
public String status;
public String message;
public String time;


...
...
// getters and setters are here
...
...
}

更新

正如@Hassan 和@cunhaf 提到的(在原始问题下的评论中) ,使用

server.error.include-message=always

ResponseStatusException完全可以正常工作。不过,如果有人想通过 Exception 传递更多信息,使用 GlobalExceptionHandler的解决方案可能会更好。

源代码

样本可以在这里找到: 全局异常处理程序

这个答案是由用户 Hassan 在对原始问题的评论中提供的。我只是把它作为一个答案发布出去,以便让它更加清晰可见。

基本上,您所需要做的就是将 server.error.include-message=always添加到 application.properties 文件中,现在应该填充消息字段。

这个行为在 Spring Boot 2.3中已经改变了,您可以在这里阅读: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.3-Release-Notes#changes-to-the-default-error-pages-content

从2.3版本开始,Spring Boot 在默认错误页面上不包含错误消息。原因是为了减少向客户泄露信息的风险

要更改默认行为,可以使用 server.error.include-message属性。

奇怪的是,Spring Boot 2.6.x 再次改变了这种行为,并且没有返回在 ResponseStatusException上设置的错误消息。为了解决这个问题,我不得不降级到2.5.6级。最后我得到了这样的东西:

 @DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.OK)
public MessageResponse deleteById(@PathVariable(value = "id") Integer id) {
try {
userService.deleteById(id);
        

} catch (Exception e) {
throw new ResponseStatusException(HttpStatus.EXPECTATION_FAILED, "Error deleting user. User has dependencies", e);
}
}

有一个 bean 可以被重写以包含自定义消息。

普通弹簧启动: org.springframework.boot.web.servlet.error.ErrorAttributes

春季结肠气流: org.springframework.boot.web.reactive.error.ErrorAttributes


默认的实现是 DefaultErrorAttributes

你可以重写 public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {

返回使它返回你想要的 message


在我的例子中,我创建了一个装饰器,它可以删除内部服务器错误的消息:

public class CustomErrorAttributesDecorator implements ErrorAttributes {


private final ErrorAttributes errorAttributes;
    

CustomErrorAttributesDecorator(ErrorAttributes errorAttributes){
this.errorAttributes = errorAttributes;
}


@Override
public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
Map<String, Object> errorAttributesMap = this.errorAttributes.getErrorAttributes(request, options);
if(HttpStatus.INTERNAL_SERVER_ERROR.value() == (int) errorAttributesMap.get("status")){
errorAttributesMap.remove("message");
}
return errorAttributesMap;
}


...
}

然后我创建了一个@Bean,如下所示:

        @Bean
ErrorAttributes customErrorAttributes(){
return new CustomErrorAttributesDecorator(new DefaultErrorAttributes());
}

如果您正在制作一个 REST API,请看一下异常处理的 zalando 问题

Evry 异常被@ExceptionHandler 捕获,你只需要使用 zalando 构建器返回一个 Problem 对象。

public class ResourceNotFoundException extends Exception {
// ...
}


@ControllerAdvice
public class MyExceptionHandler implements ProblemHandling {


@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public ResponseEntity<Problem> handleResourceNotFoundException(final ResourceNotFoundException exception, final NativeWebRequest request) {
Problem problem = Problem.builder()//
.withType(URI.create("https://example.com/problem/not-found"))//
.withTitle("Resource not found")//
.withStatus(Status.NOT_FOUND)//
.withDetail(exception.getMessage()).build();
}
        

...
}

对于那些担心使用已接受答案所推荐的 application.properties 可能泄漏敏感信息的人,我发现对于 server.error.include-message属性还有其他选项可用。

在引发异常时,设置 server.error.include-message=always将始终包含一个原因,即使该原因可能会暴露有关应用程序的敏感信息。

更好的选择是使用 server.error.include-message=on-param,它只在通过 ResponseStatusException显式设置消息时包含一条消息:

if (requestBody.someField == null) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "someField is required.");
}