Spring MVC REST Handing Bad Url (404), возвращая JSON

Я разрабатываю службу REST с помощью SpringMVC, где у меня есть @RequestMapping на уровне класса и метода.

В настоящее время это приложение настроено на возврат jsp-страницы ошибок, настроенной в web.xml.

<error-page>
    <error-code>404</error-code>
    <location>/resourceNotFound</location>
</error-page>

Однако я хочу вернуть пользовательский JSON вместо этой страницы с ошибкой.

Я могу обрабатывать исключение и возвращать json для других исключений, написав это в контроллере, но не уверен, как и где писать логику, чтобы возвращать JSON, когда URL-адрес вообще не существует.

    @ExceptionHandler(TypeMismatchException.class)
        @ResponseStatus(value=HttpStatus.NOT_FOUND)
        @ResponseBody
        public ResponseEntity<String> handleTypeMismatchException(HttpServletRequest req, TypeMismatchException ex) {

            HttpHeaders headers = new HttpHeaders();
            headers.add("Content-Type", "application/json; charset=utf-8");
            Locale locale = LocaleContextHolder.getLocale();
            String errorMessage = messageSource.getMessage("error.patient.bad.request", null, locale);

            errorMessage += ex.getValue();
            String errorURL = req.getRequestURL().toString();

            ErrorInfo errorInfo = new ErrorInfo(errorURL, errorMessage);
            return new ResponseEntity<String>(errorInfo.toJson(), headers, HttpStatus.BAD_REQUEST);

        }

Я попробовал @ControllerAdvice, он работает для других сценариев исключений, но не тогда, когда сопоставление не avaialble,

@ControllerAdvice
public class RestExceptionProcessor {

    @Autowired
    private MessageSource messageSource;

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseStatus(value=HttpStatus.NOT_FOUND)
    @ResponseBody
    public ResponseEntity<String> requestMethodNotSupported(HttpServletRequest req, HttpRequestMethodNotSupportedException ex) {
        Locale locale = LocaleContextHolder.getLocale();
        String errorMessage = messageSource.getMessage("error.patient.bad.id", null, locale);

        String errorURL = req.getRequestURL().toString();

        ErrorInfo errorInfo = new ErrorInfo(errorURL, errorMessage);
        return new ResponseEntity<String>(errorInfo.toJson(), HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(NoSuchRequestHandlingMethodException.class)
    @ResponseStatus(value=HttpStatus.NOT_FOUND)
    @ResponseBody
    public ResponseEntity<String> requestHandlingMethodNotSupported(HttpServletRequest req, NoSuchRequestHandlingMethodException ex) {
        Locale locale = LocaleContextHolder.getLocale();
        String errorMessage = messageSource.getMessage("error.patient.bad.id", null, locale);

        String errorURL = req.getRequestURL().toString();

        ErrorInfo errorInfo = new ErrorInfo(errorURL, errorMessage);
        return new ResponseEntity<String>(errorInfo.toJson(), HttpStatus.BAD_REQUEST);
    }


}

Ответ 1

После того, как вы распаковались в DispatcherServlet и HttpServletBean.init() в SpringFramework, я вижу, что это возможно в Spring 4.

org.springframework.web.servlet.DispatcherServlet

/** Throw a NoHandlerFoundException if no Handler was found to process this request? **/
private boolean throwExceptionIfNoHandlerFound = false;

protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (pageNotFoundLogger.isWarnEnabled()) {
        String requestUri = urlPathHelper.getRequestUri(request);
        pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + requestUri +
                "] in DispatcherServlet with name '" + getServletName() + "'");
    }
    if(throwExceptionIfNoHandlerFound) {
        ServletServerHttpRequest req = new ServletServerHttpRequest(request);
        throw new NoHandlerFoundException(req.getMethod().name(),
                req.getServletRequest().getRequestURI(),req.getHeaders());
    } else {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
    }
}

throwExceptionIfNoHandlerFound по умолчанию ложно, и мы должны включить это в web.xml

<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>throwExceptionIfNoHandlerFound</param-name>
            <param-value>true</param-value>
        </init-param>
    <load-on-startup>1</load-on-startup>
    <async-supported>true</async-supported>
</servlet>

И затем вы можете поймать его в классе, аннотированном с помощью @ControllerAdvice, используя этот метод.

@ExceptionHandler(NoHandlerFoundException.class)
@ResponseStatus(value=HttpStatus.NOT_FOUND)
@ResponseBody
public ResponseEntity<String> requestHandlingNoHandlerFound(HttpServletRequest req, NoHandlerFoundException ex) {
    Locale locale = LocaleContextHolder.getLocale();
    String errorMessage = messageSource.getMessage("error.bad.url", null, locale);

    String errorURL = req.getRequestURL().toString();

    ErrorInfo errorInfo = new ErrorInfo(errorURL, errorMessage);
    return new ResponseEntity<String>(errorInfo.toJson(), HttpStatus.BAD_REQUEST);
}

Что позволяет мне возвращать ответ JSON для плохих URL-адресов, для которых нет сопоставления, вместо перенаправления на страницу JSP:)

{"message":"URL does not exist","url":"http://localhost:8080/service/patientssd"}

Ответ 2

Если вы используете Spring Boot, установите ОБА из этих двух свойств:

spring.resources.add-mappings=false
spring.mvc.throw-exception-if-no-handler-found=true

Теперь ваш аннотированный класс @ControllerAdvice может обрабатывать "NoHandlerFoundException", как показано ниже.

@ControllerAdvice
@RequestMapping(produces = "application/json")
@ResponseBody
public class RestControllerAdvice {

    @ExceptionHandler(NoHandlerFoundException.class)
    public ResponseEntity<Map<String, Object>> unhandledPath(final NoHandlerFoundException e) {
        Map<String, Object> errorInfo = new LinkedHashMap<>();
        errorInfo.put("timestamp", new Date());
        errorInfo.put("httpCode", HttpStatus.NOT_FOUND.value());
        errorInfo.put("httpStatus", HttpStatus.NOT_FOUND.getReasonPhrase());
        errorInfo.put("errorMessage", e.getMessage());
        return new ResponseEntity<Map<String, Object>>(errorInfo, HttpStatus.NOT_FOUND);
    }

}

обратите внимание, что недостаточно указать ТОЛЬКО это свойство:

spring.mvc.throw-exception-if-no-handler-found=true

поскольку по умолчанию Spring отображает неизвестные URL-адреса в /**, так что на самом деле "обработчик не найден".

Чтобы отключить отображение неизвестного URL в /**, вам нужно

spring.resources.add-mappings=false ,

именно поэтому два свойства вместе производят желаемое поведение.

Ответ 3

Если вы используете spring 3.2 или более позднюю версию, вы можете использовать советник контроллера (@ControllerAdvice), чтобы, среди прочего, сопоставлять ошибки (404). Здесь вы можете найти документацию. Взгляните на раздел 17.11. Вы можете использовать это, например, для более подробного ведения журнала о том, почему привязки запросов не соответствуют конкретным URL-адресам или просто возвращают более конкретный ответ, чем общий 404.

Ответ 4

вы можете вернуть json в нижеуказанное расположение, /handle/404.

<error-page>
    <error-code>404</error-code>
    <location>/handle/404</location>
</error-page>

после того, как вы настроите это в web.xml, ошибка 404 будет перенаправлена ​​на /handle/404, и вы можете создать контроллер с этим сопоставлением и вернуть результат json. например.

@RestController
@RequestMapping(value = "handle")
public class HttpErrorController {
   @RequestMapping(value = "404")
   public String handle404() {
      return "404 error";
   }
}