Не все исключения Spring Boot можно обрабатывать единообразно | Заметки по отладке Java

Spring Boot Java
Не все исключения Spring Boot можно обрабатывать единообразно | Заметки по отладке Java

Эта статья участвует в "Месяце тем Java - Заметки по отладке Java", подробности см.Ссылка на мероприятие

Обычно унифицированная обработка исключений, которую мы настроили в Spring Boot (@RestControllerAdviceСотрудничать@ExceptionHandlerреализация) может обрабатывать только исключения, генерируемые контроллером. Некоторые запросы имеют исключения до того, как они достигают контроллера, и эти исключения не могут быть перехвачены унифицированными исключениями, такими как некоторые исключения в контейнере сервлета. Сегодня я столкнулся с одним в разработке проекта, который меня очень огорчил, потому что формат возвращаемого им сообщения об ошибке не может быть обработан единообразно, поэтому я решил найти решение для решения этой проблемы.

ErrorPageFilter

Whitelabel Error Page

Я считаю, что такая картина не редкость.Пока Spring Boot делает ошибку, это то, что отражается на странице. Если вы получаете исключение с чем-то вроде Postman:

{
  "timestamp": "2021-04-29T22:45:33.231+0000",
  "status": 500,
  "message": "Internal Server Error",
  "path": "foo/bar"
}

Как это достигается? Spring Boot регистрируетErrorPageFilter, когда в сервлете возникает исключение, фильтр перехватит обработку и обработает исключение по разным стратегиям:Если исключение уже обрабатывается, обработайте его напрямую, в противном случае перенаправьте его на соответствующую страницу с ошибкой..有兴趣的可以去看下源码,逻辑不复杂,这里就不贴了。

Кроме того, когда сервлет выдает аномалию, исключение службы доступно изHttpServletRequestПолучите несколько свойств, как показано ниже:

异常属性

Мы можем получить информацию об исключении из нескольких свойств выше.

Страница ошибки по умолчанию

Обычно Spring Boot переходит к значению по умолчанию, когда возникает исключение/errorобрабатывается, пока/errorСоответствующая логика задается выражениемBasicErrorControllerосуществленный.

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
    //返回错误页面
  @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
	public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
		HttpStatus status = getStatus(request);
		Map<String, Object> model = Collections
				.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
		response.setStatus(status.value());
		ModelAndView modelAndView = resolveErrorView(request, response, status, model);
		return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
	}
    // 返回json
	@RequestMapping
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
		HttpStatus status = getStatus(request);
		if (status == HttpStatus.NO_CONTENT) {
			return new ResponseEntity<>(status);
		}
		Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
		return new ResponseEntity<>(body, status);
	}  
// 其它省略
}

И соответствующая конфигурация:

@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
      ObjectProvider<ErrorViewResolver> errorViewResolvers) {
   return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
         errorViewResolvers.orderedStream().collect(Collectors.toList()));
}

Итак, нам просто нужно заново реализоватьErrorControllerА внедрение Spring IoC может заменить механизм обработки по умолчанию. И мы можем ясно найти этоBasicErrorControllerне толькоErrorControllerРеализация также является контроллером.Если мы позволим методу контроллера генерировать исключение, оно определенно может быть обработано пользовательским унифицированным исключением. так что я правBasicErrorControllerОбновлено:

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class ExceptionController extends AbstractErrorController {


    public ExceptionController(ErrorAttributes errorAttributes) {
        super(errorAttributes);
    }


    @Override
    @Deprecated
    public String getErrorPath() {
        return null;
    }

    @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        throw new RuntimeException(getErrorMessage(request));
    }

    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        throw new RuntimeException(getErrorMessage(request));
    }

    private String getErrorMessage(HttpServletRequest request) {
        Object code = request.getAttribute("javax.servlet.error.status_code");
        Object exceptionType = request.getAttribute("javax.servlet.error.exception_type");
        Object message = request.getAttribute("javax.servlet.error.message");
        Object path = request.getAttribute("javax.servlet.error.request_uri");
        Object exception = request.getAttribute("javax.servlet.error.exception");

        return String.format("code: %s,exceptionType: %s,message: %s,path: %s,exception: %s",
                code, exceptionType, message, path, exception);
    }
}

Непосредственное создание исключений — это просто и экономно! Большинство пойманных здесь исключений не прошли через Контроллер, мы проходимExceptionControllerRelay также позволяет единообразно обрабатывать эти исключения, гарантируя, что обработка исключений во всем приложении поддерживает единый внешний вид. Я не знаю, есть ли у вас лучший способ, добро пожаловать, чтобы оставить сообщение для обсуждения.