Глобальная обработка исключений и пользовательская страница 404 в SpringBoot

Spring Boot

1. Анализ принципа обработки ошибок

В веб-проекте, созданном с помощью SpringBoot, когда запрошенная нами страница не существует (код состояния HTTP — 404) или когда возникает исключение (код состояния HTTP обычно равен 500), SpringBoot вернет нам сообщение об ошибке.

Другими словами, в веб-проекте SpringBoot автоматически создается интерфейс ошибки /error для возврата информации об ошибке. Однако для разных методов доступа будут следующие две разные возвращаемые данные. Это в основном зависит от информации заголовка http при посещенииAcceptЭто значение указывает, какие типы вы можете получать

  • Информация заголовка и возвращаемый результат при доступе с помощью браузера
Accept: text/html

  • Используйте другие устройства, такие как мобильные клиенты, для доступа к информации о заголовке и его возвращаемых результатах (обычно в разделенной архитектуре внешнего и внутреннего интерфейса).
Accept: */*

2. Обработка ошибок

Существует два основных способа обработки исключений:

1. Обработка исключений с использованием принципа автоконфигурации SpringBoot

SpringBoot автоматически настраивает классErrorMvcAutoConfigurationЧтобы обрабатывать исключения, вы можете посмотреть, если вам интересно, а затем определить ошибочный класс BasicErrorController в этом классе.Основной код выглядит следующим образом:

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

  	/**
  	 * 错误的页面响应 
  	 */
    @RequestMapping(produces = {"text/html"})
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
      	// 得到一个modelAndView对象
        ModelAndView modelAndView = this.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 = this.getStatus(request);
        if (status == HttpStatus.NO_CONTENT) {
            return new ResponseEntity(status);
        } else {
            Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
            return new ResponseEntity(body, status);
        }
    }
}

Я не буду вдаваться в слишком много кода, и если вам интересно, вы можете взглянуть. Приведенный выше код означает, что для разных методов запроса будут возвращены разные результаты.@RequestMappingаннотированныйproduces = {"text/html"}атрибут

1), вернуть страницу с ошибкой, например 404, 500 и т. д.

  • В случае механизма шаблонов (который можно использовать для рендеринга страниц)

В качестве рендеринга страницы в проекте используются шаблонизаторы, такие как: thymeleaf, freemarker и т.д. Создайте папку /error в шаблонах и добавьте файл .html, соответствующий коду состояния ошибки, как показано ниже:

404 и 500 здесь являются определенными кодами состояния ошибки, а 4xx указывает на другие ошибки, начинающиеся с 4, такие как 400, 401 и т. д. Конечно, вы можете установить соответствующую страницу ошибки для каждого кода состояния, но в этом нет никакой пользы, поэтому вместо этого вы можете напрямую использовать общую ссылку, такую ​​​​как 4xx.html.

На нашей странице ошибки можно получить следующую информацию (то есть содержимое объекта ModelAndView):

имя поля иллюстрировать
timstamp отметка времени
status код состояния ошибки
error Сообщение об ошибке
exception объект исключения
message Ненормальное сообщение
path путь к странице

Внимательные друзья обнаружат, что это на самом деле содержимое json, которое возвращается, когда вы запрашиваете его с помощью своего мобильного телефона.

Например: добавьте вышеуказанную информацию в код, а затем пропишите код ошибки в бэкенде:

@RequestMapping("haserror")
@ResponseBody
public Object myError(){
  int i =10/0;
  return "something is error";
}
这是一个错误页面:
<ul>
    <li>错误状态码:[[${status}]]</li>
    <li>错误消息:[[${error}]]</li>
    <li>异常对象:[[${exception}]]</li>
    <li>异常消息:[[${message}]]</li>
    <li>当前时间:[[${timestamp}]]</li>
</ul>

  • Без шаблонизатора

Когда движок шаблонов не используется в проекте, просто переместите всю папку ошибок в папку static.

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

2) и возвращает соответствующую строку json

По этому поводу сказать нечего, возвращается строка json. Формат следующий:

{
"timestamp": "2020-04-22T16:13:37.506+0000",
"status": 500,
"error": "Internal Server Error",
"message": "/ by zero",
"path": "/hello/haserror",
"reason": "完了,你写的代码又产生了一次线上事故"
}

3), информация о возврате пользовательской страницы

Это самый важный контент, потому что эта информация не только возвращается в виде json, но также может быть получена на странице с ошибкой выше или может быть возвращена напрямую в формате json. На самом деле это очень просто, то есть добавитьErrorAttributesС объектом все в порядке, здесь я решил наследовать его подкласс.

@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        //调用父类的方法,会自动获取内置的那些属性,如果你不想要,可以不调用这个
        Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);

        //添加自定义的属性
        errorAttributes.put("reason","完了,你写的代码又产生了一次线上事故");
        // 你可以看一下这个方法的参数webRequest这个对象,我相信你肯定能发现好东西

        return errorAttributes;
    }
}

Вот и все, проверьте, доступно ли одно из наших пользовательских свойств в двух методах запроса:

2. Используйте уведомление об исключении AOP для обработки (рекомендуется)

Его принцип заключается в том, чтобы получить глобальное уведомление об исключении и затем обработать его. Нам нужно только написать следующий код в проекте (на самом деле, это просто класс для пользовательской информации об исключении)

@ControllerAdvice
public class ErrroAcvice {

    /**
     * 全局捕获异常的切面类
     * @param request 请求对象,可不传
     * @param response 响应对象,可不传
     * @param e 异常类(这个要和你当前捕获的异常类是同一个)
     */
    @ExceptionHandler(Exception.class) //也可以只对一个类进行捕获
    public void errorHandler(HttpServletRequest request, HttpServletResponse response,Exception e){
      	/*
      	 * You can do everything you want to do
         * 这里你拿到了request和response对象,你可以做任何你想做的事
         * 比如:
         *	1.用request从头信息中拿到Accept来判断是请求方可接收的类型从而进行第一个方法的判断
         *	2.如果你也想返回一个页面,使用response对象进行重定向到自己的错误页面就可以了
         *  3.你甚至还拿到了异常对象
      	 */
      
        String accept = request.getHeader("Accept");
				// 根据这个字符串来判断做出什么响应	
      
        try {
            response.setStatus(500);
            response.getWriter().write("hello");
        } catch (IOException ex) {
            ex.printStackTrace();
        }
      
    }
}

3. Сравнение двух методов:

  • Первый способ заключается в том, чтобы поместить несколько кодовых страниц состояния ошибки в текущий проект и позволить SpringBoot найти их. Также поддерживает настраиваемые возвращаемые сообщения об ошибках.
  • Второй метод заключается в непосредственном использовании идеи АОП для обработки уведомлений об исключениях, что очень бесплатно.
  • Я лично рекомендую использовать второй метод, потому что степень свободы высока, вы можете в любой момент внести изменения в соответствии с собственной бизнес-логикой, и в этом есть большая польза. В следующей статье будет хороший пример
  • После использования второго метода страницы ошибок и пользовательские сообщения об ошибках, размещенные первым методом, становятся недействительными.