如何替换Spring Boot中的默认错误控制器?
自定义Spring Boot的/error JSON响应控制器问题
我原本以为可以直接编写自己的/error控制器,结果Spring Boot并不允许。以下是最小可复现示例(MRE):
package com.example.helloworldmre.controller; import com.example.helloworldmre.data.FailureMessage; import org.springframework.context.annotation.Primary; 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 @RequestMapping("/error") @Primary public class ErrorController { @RequestMapping public FailureMessage errorResponse(HttpServletRequest request) { return new FailureMessage(HttpMethod.valueOf(request.getMethod()), request.getRequestURI()); // 一个简单的包含错误信息的类,为简洁起见不贴代码 } }
报错信息如下:
Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'basicErrorController' method org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest) to { [/error]}: There is already 'errorController' bean method com.example.helloworldmre.controller.ErrorController#errorResponse(HttpServletRequest) mapped. at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry.validateMethodMapping(AbstractHandlerMethodMapping.java:669) ~[spring-webmvc-5.3.16.jar:5.3.16] ... 50 common frames omitted
我尝试禁用白标错误端点,但没有效果:
server.error.whitelabel.enabled=false
我还尝试继承BasicErrorController:
package com.example.helloworldmre.controller; import com.example.helloworldmre.data.FailureMessage; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.boot.autoconfigure.web.ErrorProperties; import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController; import org.springframework.boot.web.servlet.error.ErrorAttributes; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import javax.servlet.http.HttpServletRequest; import java.util.Map; public class ErrorController extends BasicErrorController { private final ObjectMapper objectMapper; public ErrorController(ObjectMapper objectMapper, ErrorAttributes errorAttributes, ErrorProperties errorProperties) { super(errorAttributes, errorProperties); this.objectMapper = objectMapper; } @Override public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { FailureMessage failureMessage = new FailureMessage(HttpMethod.valueOf(request.getMethod()), request.getRequestURI()); Map<String, Object> failureMessageAsMap = objectMapper.convertValue(failureMessage, new TypeReference<>() { }); return ResponseEntity.badRequest().body(failureMessageAsMap); } }
结果出现以下错误:
2023-12-07 14:14:27.965 ERROR 6336 --- [nio-8090-exec-2] o.a.c.c.C.[Tomcat].[localhost] : Exception Processing ErrorPage[errorCode=0, location=/error] javax.servlet.ServletException: Circular view path [error]: would dispatch back to the current handler URL [/error] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.) ... 50 common frames omitted
需求说明:我希望返回简单的JSON格式响应,而非完整的HTML视图,请问正确的实现方式是什么?
正确实现方式
方法1:实现ErrorController接口并替换默认Bean
Spring Boot允许通过实现ErrorController接口自定义错误处理,同时需将类注册为Bean,通过排除默认BasicErrorController避免冲突:
package com.example.helloworldmre.controller; import com.example.helloworldmre.data.FailureMessage; import org.springframework.boot.web.servlet.error.ErrorController; import org.springframework.context.annotation.Primary; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; @RestController @Primary public class CustomErrorController implements ErrorController { private static final String ERROR_PATH = "/error"; @RequestMapping(value = ERROR_PATH, produces = MediaType.APPLICATION_JSON_VALUE) public FailureMessage handleError(HttpServletRequest request) { return new FailureMessage( HttpMethod.valueOf(request.getMethod()), request.getRequestURI() ); } // Spring Boot 2.3+ 已弃用 getErrorPath(),无需实现 }
同时在启动类中排除默认自动配置:
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration; @SpringBootApplication(exclude = ErrorMvcAutoConfiguration.class) public class HelloWorldMreApplication { public static void main(String[] args) { SpringApplication.run(HelloWorldMreApplication.class, args); } }
方法2:使用@RestControllerAdvice全局异常处理(推荐)
若仅需统一处理异常并返回JSON,无需接管/error端点,全局异常处理器更灵活:
package com.example.helloworldmre.controller; import com.example.helloworldmre.data.FailureMessage; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import javax.servlet.http.HttpServletRequest; @RestControllerAdvice public class GlobalExceptionHandler { // 处理所有未捕获的异常 @ExceptionHandler(Exception.class) public ResponseEntity<FailureMessage> handleAllExceptions(HttpServletRequest request, Exception ex) { FailureMessage failureMessage = new FailureMessage( HttpMethod.valueOf(request.getMethod()), request.getRequestURI() ); return ResponseEntity.status(500).body(failureMessage); } // 针对特定异常单独处理 @ExceptionHandler(IllegalArgumentException.class) public ResponseEntity<FailureMessage> handleIllegalArgument(HttpServletRequest request, IllegalArgumentException ex) { FailureMessage failureMessage = new FailureMessage( HttpMethod.valueOf(request.getMethod()), request.getRequestURI() ); return ResponseEntity.badRequest().body(failureMessage); } }
@RestControllerAdvice默认返回JSON,Spring Boot会自动将异常转发到处理器,无需修改/error端点。
方法3:正确继承BasicErrorController
若必须继承BasicErrorController,需注册为Bean并指定返回JSON格式,避免视图解析冲突:
package com.example.helloworldmre.controller; import com.example.helloworldmre.data.FailureMessage; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.boot.autoconfigure.web.ErrorProperties; import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController; import org.springframework.boot.web.servlet.error.ErrorAttributes; import org.springframework.context.annotation.Primary; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import java.util.Map; @RestController @Primary public class CustomErrorController extends BasicErrorController { private final ObjectMapper objectMapper; public CustomErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, ObjectMapper objectMapper) { super(errorAttributes, errorProperties); this.objectMapper = objectMapper; } @Override @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { FailureMessage failureMessage = new FailureMessage( HttpMethod.valueOf(request.getMethod()), request.getRequestURI() ); Map<String, Object> response = objectMapper.convertValue(failureMessage, Map.class); int statusCode = getStatus(request).value(); return ResponseEntity.status(statusCode).body(response); } }
此方式明确指定返回JSON,避免视图解析器寻找"error"视图文件,解决循环路径问题。
内容的提问来源于stack exchange,提问作者Sergey Zolotarev
相关产品推荐
相关产品推荐

