要使其成为完整的 MRE,只需使用 Web 启动器 Lombok 创建一个基本的 Boot 项目并包含以下类。在我的项目中,我使用 Spring Boot 2.6.4,但我相信这对于这个问题的目的来说并不重要
如果您的 Web 应用程序中未包含任何自定义异常逻辑并向不存在的端点发出请求,您将获得与此类似的 JSON(假设请求的标头不包含
Accept: text/html
,在在这种情况下,您将获得一个白标签 html 页面):
{
"timestamp": "2024-02-09T05:02:23.763+00:00",
"status": 404,
"error": "Not Found",
"path": "/bogus-path"
}
这还不错,但是如果我想要更多的控制权怎么办(我并不是说用像
server.error.include-stacktrace
这样的属性稍微调整 JSON)?
为了拥有自己的异常处理逻辑,我编写了自己的简单
ErrorController
:
import com.example.helloworldmre.data.FailureMessage;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
public class MyErrorController implements ErrorController {
@RequestMapping("/error")
public FailureMessage error(HttpServletRequest request) {
return new FailureMessage(HttpMethod.valueOf(request.getMethod()), request.getRequestURI());
}
}
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import org.springframework.http.HttpMethod;
@Getter
public class FailureMessage extends Message {
@JsonProperty("request_path")
private final String requestPath;
private final HttpMethod method;
public FailureMessage(HttpMethod method, String requestPath) {
super("Could not perform the request");
this.method = method;
this.requestPath = requestPath;
}
}
import lombok.Getter;
@Getter
public abstract class Message {
private final String message;
public Message(String message) {
this.message = message;
}
}
我的目的是让它在每次发生不好的事情时返回这样的响应(例如对不存在的端点的请求):
{
"message": "Could not perform the request",
"method": "POST",
"request_path": "/some-bogus-path"
}
相反,它总是返回以下响应:
{
"message": "Could not perform the request",
"method": "GET",
"request_path": "/error"
}
换句话说,我捕获的是重定向的路径和方法,而不是原始请求
这篇Baeldung文章建议你自己写
HandlerExceptionResolver
@Component
public class RestResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver {
@Override
protected ModelAndView doResolveException(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
return null; // breakpoint here
}
}
但是断点从未被命中,并且响应与 Boot 中的默认 JSON 相同
我也尝试过这个:
@RestControllerAdvice
public class MyErrorController {
@ExceptionHandler(Exception.class)
public FailureMessage error(HttpServletRequest request) {
return new FailureMessage(HttpMethod.valueOf(request.getMethod()), request.getRequestURI());
}
}
同样是默认的 JSON。我想,当接收到不存在的端点的请求时,Boot 不会抛出任何异常,因此处理程序不会启动
总而言之,我如何自己处理异常,以涉及访问的方式返回自定义 JSON:
在 Boot 中,正确的方法是扩展
DefaultErrorAttributes
并编译您自己的属性映射,如下所示:
package com.example.helloworldmre.config;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import javax.servlet.RequestDispatcher;
import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
@Component
public class CustomErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(
WebRequest webRequest, ErrorAttributeOptions options) {
Map<String, Object> errorAttributes =
new LinkedHashMap<>();
addError(errorAttributes, webRequest);
addMethod(errorAttributes, webRequest);
addPath(errorAttributes, webRequest);
addTimestamp(errorAttributes, webRequest);
return errorAttributes;
}
private void addError(Map<String, Object> errorAttributes, WebRequest webRequest) {
Optional<Integer> optionalStatus = Optional.ofNullable(
(Integer) webRequest.getAttribute(RequestDispatcher.ERROR_STATUS_CODE, RequestAttributes.SCOPE_REQUEST)
);
String errorDescription;
if (optionalStatus.isPresent()) {
Integer statusCode = optionalStatus.get();
String reasonPhrase = HttpStatus.valueOf(statusCode).getReasonPhrase();
errorDescription = MessageFormat.format("{0} {1}", statusCode, reasonPhrase);
} else errorDescription = "Unknown";
errorAttributes.put("error", errorDescription);
}
private void addMethod(Map<String, Object> errorAttributes, WebRequest webRequest) {
errorAttributes.put("method", ((ServletWebRequest) webRequest).getHttpMethod());
}
private void addPath(Map<String, Object> errorAttributes, WebRequest webRequest) {
Object path = webRequest.getAttribute(RequestDispatcher.ERROR_REQUEST_URI, RequestAttributes.SCOPE_REQUEST);
errorAttributes.put("path", path);
}
private void addTimestamp(Map<String, Object> errorAttributes, WebRequest webRequest) {
errorAttributes.put("timestamp", LocalDateTime.now());
}
}
现在,我的错误 JSON 看起来像这样
{
"error": "404 Not Found",
"method": "GET",
"path": "/bogus-path",
"timestamp": "2024-02-09T10:09:31.1491971"
}
尽管我仍然感觉自己是在遵守 Spring Boot 的规则