我已经构建了一个错误控制器,该错误控制器应该是在Spring REST服务中捕获异常的“最后一行”。但是,似乎我不能返回POJO作为响应类型。为什么 jackson 在这种情况下不起作用?

我的班级看起来像:

@RestController
public class CustomErrorController implements ErrorController
{
  private static final String PATH = "/error";

  @Override
  public String getErrorPath()
  {
     return PATH;
  }


  @RequestMapping (value = PATH)
  public ResponseEntity<WebErrorResponse> handleError(HttpStatus status, HttpServletRequest request)
  {
     WebErrorResponse response = new WebErrorResponse();

    // original requested URI
    String uri = String.valueOf(request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI));
    // status code
    String code = String.valueOf(request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE));
    // status message
    String msg = String.valueOf(request.getAttribute(RequestDispatcher.ERROR_MESSAGE));

    response.title = "Internal Server Error";
    response.type = request.getMethod() + ": " + uri;
    response.code = Integer.valueOf(code);
    response.message = msg;

    // build headers
    HttpHeaders headers = new HttpHeaders();

    headers.setContentType(MediaType.APPLICATION_JSON_UTF8);

    // build the response
    return new ResponseEntity<>(response, headers, status);
}

public class WebErrorResponse
{
/**
 * The error message.
 */
public String message;

/**
 * The status code.
 */
public int code;

/**
 * The error title.
 */
public String title;

/**
 * The error type.
 */
public String type;
}

这应该可行,但是唯一的响应是带有以下内容的码头错误消息
406-不可接受。

将响应实体主体类型更改为String可以完美工作。
怎么了?也许这是一个错误?

附言:使用Spring 4.2.8,Spring Boot 1.3.8。

最佳答案

最终解决方案

经过Google的多次尝试和错误循环以及往返之后,我终于找到了可以满足我需要的解决方案。 Spring中错误处理的主要问题是由默认行为和小的文档引起的。

仅使用不带Spring Boot的Spring没问题。但是同时使用两者来构建
Web(REST)服务就像 hell 。

因此,我想分享我的解决方案,以帮助遇到相同问题的所有人...

您将需要的是:

  • 一个Spring Java配置类
  • spring的异常处理程序(使用@ControllerAdvice并扩展ResponseEntityExceptionHandler)
  • 一个错误控制器(使用@Controller并扩展AbstractErrorController)
  • 一个简单的POJO,可通过Jackson(可选)生成错误响应

  • 配置(切除重要部分)
    @Configuration
    public class SpringConfig extends WebMvcConfigurerAdapter
    {
       // ... init stuff if needed
    
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer)
    {
        // setup content negotiation (automatic detection of content types)
        configurer
                // use format parameter and extension to detect mimetype
                .favorPathExtension(true).favorParameter(true)
                // set default mimetype
                .defaultContentType(MediaType.APPLICATION_XML)
                .mediaType(...)
                // and so on ....
     }
    
     /**
     * Configuration of the {@link DispatcherServlet} bean.
     *
     * <p>This is needed because Spring and Spring Boot auto-configuration override each other.</p>
     *
     * @see <a href="http://stackoverflow.com/questions/28902374/spring-boot-rest-service-exception-handling">
     *      Stackoverflow - Spring Boot REST service exception handling</a>
     *
     * @param dispatcher dispatcher servlet instance
     */
    @Autowired
    @SuppressWarnings ("SpringJavaAutowiringInspection")
    public void setupDispatcherServlet(DispatcherServlet dispatcher)
    {
        // FIX: for global REST error handling
        // enable exceptions if endpoint not found (instead of static error page)
        dispatcher.setThrowExceptionIfNoHandlerFound(true);
    }
    
    /**
     * Creates the error properties used to setup the global REST error controller.
     *
     * <p>Using {@link ErrorProperties} is compliant to base implementation if Spring Boot's
     * {@link org.springframework.boot.autoconfigure.web.BasicErrorController}.</p>
     *
     *
     * @return error properties
     */
    @Bean
    public ErrorProperties errorProperties()
    {
        ErrorProperties properties = new ErrorProperties();
    
        properties.setIncludeStacktrace(ErrorProperties.IncludeStacktrace.NEVER);
        properties.setPath("/error");
    
        return properties;
    }
    // ...
    }
    

    Spring异常处理程序:
    @ControllerAdvice(annotations = RestController.class)
    public class WebExceptionHandler extends ResponseEntityExceptionHandler
    {
    /**
     * This function handles the exceptions.
     *
     * @param e the thrown exception
     *
     * @return error message as XML-document
     */
    @ExceptionHandler (Exception.class)
    public ResponseEntity<Object> handleErrorResponse(Exception e)
    {
        logger.trace("Catching Exception in REST API.", e);
    
        return handleExceptionInternal(e, null, null, null, null);
    }
    
    @Override
    protected ResponseEntity<Object> handleExceptionInternal(Exception ex,
                                                             Object body,
                                                             HttpHeaders headers,
                                                             HttpStatus status,
                                                             WebRequest request)
    {
        logger.trace("Catching Spring Exception in REST API.");
        logger.debug("Using " + getClass().getSimpleName() + " for exception handling.");
    
        // fatal, should not happen
        if(ex == null) throw new NullPointerException("empty exception");
    
        // set defaults
        String title = "API Error";
        String msg   = ex.getMessage();
    
        if(status == null) status = HttpStatus.BAD_REQUEST;
    
        // build response body
        WebErrorResponse response = new WebErrorResponse();
    
        response.type = ex.getClass().getSimpleName();
        response.title = title;
        response.message = msg;
        response.code = status.value();
    
        // build response headers
        if(headers == null) headers = new HttpHeaders();
    
        try {
            headers.setContentType(getContentType(request));
        }
        catch(NullPointerException e)
        {
            // ignore (empty headers will result in default)
        }
        catch(IllegalArgumentException e)
        {
            // return only status code
            return new ResponseEntity<>(status);
        }
    
        return new ResponseEntity<>(response, headers, status);
    }
    
    /**
     * Checks the given request and returns the matching response content type
     * or throws an exceptions if the requested content type could not be delivered.
     *
     * @param request current request
     *
     * @return response content type matching the request
     *
     * @throws NullPointerException     if the request does not an accept header field
     * @throws IllegalArgumentException if the requested content type is not supported
     */
    private static MediaType getContentType(WebRequest request) throws NullPointerException, IllegalArgumentException
    {
        String accepts = request.getHeader(HttpHeaders.ACCEPT);
    
        if(accepts==null) throw new NullPointerException();
    
        // XML
        if(accepts.contains(MediaType.APPLICATION_XML_VALUE) ||
           accepts.contains(MediaType.TEXT_XML_VALUE) ||
           accepts.contains(MediaType.APPLICATION_XHTML_XML_VALUE))
            return MediaType.APPLICATION_XML;
        // JSON
        else if(accepts.contains(MediaType.APPLICATION_JSON_VALUE))
            return MediaType.APPLICATION_JSON_UTF8;
        // other
        else throw new IllegalArgumentException();
    }
    }
    

    以及Spring Boot的错误控制器:
    @Controller
    @RequestMapping("/error")
    public class CustomErrorController extends AbstractErrorController
    {
        protected final Logger logger = LoggerFactory.getLogger(getClass());
    
        /**
         * The global settings for this error controller.
         */
        private final ErrorProperties properties;
    
        /**
         * Bean constructor.
         *
         * @param properties global properties
         * @param attributes default error attributes
         */
        @Autowired
        public CustomErrorController(ErrorProperties properties, ErrorAttributes attributes)
        {
            super(attributes);
    
            this.properties = new ErrorProperties();
        }
    
        @Override
        public String getErrorPath()
        {
            return this.properties.getPath();
        }
    
        /**
         * Returns the configuration properties of this controller.
         *
         * @return error properties
         */
        public ErrorProperties getErrorProperties()
        {
            return this.properties;
        }
    
        /**
         * This function handles runtime and application errors.
         *
         * @param request the incorrect request instance
         *
         * @return error message as XML-document
         */
        @RequestMapping (produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_XML_VALUE})
        @ResponseBody
        public ResponseEntity<Object> handleError(HttpServletRequest request)
        {
            logger.trace("Catching Exception in REST API.");
            logger.debug("Using {} for exception handling." , getClass().getSimpleName());
    
            // original requested REST endpoint
            String endpoint = String.valueOf(request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI));
            // status code
            String code = String.valueOf(request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE));
            // thrown exception
            Exception ex = ((Exception) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION));
    
            if(ex == null) {
                ex = new RuntimeException(String.valueOf(request.getAttribute(RequestDispatcher.ERROR_MESSAGE)));
            }
    
            // release nested exceptions (we want source exception only)
            if(ex instanceof NestedServletException && ex.getCause() instanceof Exception) {
                ex = (Exception) ex.getCause();
            }
    
            // build response body
            WebErrorResponse response = new WebErrorResponse();
    
            response.title   = "Internal Server Error";
            response.type    = ex.getClass().getSimpleName();
            response.code    = Integer.valueOf(code);
            response.message = request.getMethod() + ": " + endpoint+"; "+ex.getMessage();
    
            // build response headers
            HttpHeaders headers = new HttpHeaders();
    
            headers.setContentType(getResponseType(request));
    
            // build the response
            return new ResponseEntity<>(response, headers, getStatus(request));
        }
    
        /*@RequestMapping (produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_XML_VALUE})
        public ResponseEntity<Map<String, Object>> handleError(HttpServletRequest request)
        {
            Boolean stacktrace = properties.getIncludeStacktrace().equals(ErrorProperties.IncludeStacktrace.ALWAYS);
    
            Map<String, Object> r = getErrorAttributes(request, stacktrace);
    
            return new ResponseEntity<Map<String, Object>>(r, getStatus(request));
        }*/
    
        /**
         * Extracts the response content type from the "Accept" HTTP header field.
         *
         * @param request request instance
         *
         * @return response content type
         */
        private MediaType getResponseType(HttpServletRequest request)
        {
            String accepts = request.getHeader(HttpHeaders.ACCEPT);
    
            // only XML or JSON allowed
            if(accepts.contains(MediaType.APPLICATION_JSON_VALUE))
                return MediaType.APPLICATION_JSON_UTF8;
            else return MediaType.APPLICATION_XML;
        }
    }
    

    就是这样,POJO WebErrorResponse是仅使用公共Strings和int字段的普通类。

    上面的类适用于支持XML和JSON的REST API。
    这个怎么运作:

    控制器(自定义和应用程序逻辑)中的
  • 异常将由Spring异常处理程序
  • 处理
    Spring的
  • 异常将由Spring异常处理程序(例如缺少的参数)处理
  • 404(缺少端点)将由Spring Boot错误控制器
  • 处理
  • mimetype问题(例如,请求图像/ png但引发异常)将首先移至Spring排除处理程序,然后重定向至Spring Boot错误控制器(由于mimetype异常)

  • 我希望这能为其他像我一样困惑的人澄清一些事情。

    最好的祝福,

    Zipunrar

    08-25 10:04
    查看更多