Spring Notes — обработка исключений

Java

Spring MVC предоставляет мне несколько способов настроить обработку исключений.

Ссылка на эту статью:Exception Handling in Spring MVC

Настройте коды состояния HTTP для исключений

По умолчанию, если мы создадим исключение в контроллере, Spring MVC ответит пользователю страницей 500 с подробной информацией об ошибке.

Если мы хотим изменить код состояния HTTP, соответствующий ошибке, мы можем добавить его в соответствующее исключение.@ResponseStatusАннотация, с помощью которой мы можем установить код состояния HTTP и сообщение об ошибке, соответствующее этому исключению, например:

@Controller
public class ExceptionController {
    @RequestMapping("/")
    public void test(){
        throw new NotFoundException();
    }
}
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "not found")
public class NotFoundException extends RuntimeException{
}

Затем спросите, вы можете обнаружить, что страница отличается:

Обработка перехвата ошибок уровня контроллера

пройти через@ResponseStatusОбратите внимание, что хотя мы можем настраивать коды состояния HTTP и сообщения об ошибках, этого недостаточно.

Во-первых, вы можете устанавливать только те исключения, которые написали сами, существующие исключения не могут быть расширены.

Во-вторых, страницу ошибки нельзя настроить, и мы практически не используем страницу ошибки по умолчанию.

Для двух вышеупомянутых проблем вы можете добавить в контроллер методы для перехвата и обработки исключений. необходимо использовать метод@ExceptionHandlerаннотация.注解后,方法会拦截токСпособ обработки запроса контроллера (по@RequestMappingаннотированный метод) выдает исключение. В то же время этот метод перехвата исключения может возвращать представление, которое используется для отображения информации об ошибке. В то же время вы также можете использовать этот метод перехвата исключений.@ResponseStatusЧтобы реализовать настройку кодов состояния HTTP для существующих исключений, см. пример:

@Controller
public class ExceptionHandlingController {
  // 请求处理方法
  ...
  
  // 异常处理方法
  
  // 定制一个已有异常的HTTP状态码
  @ResponseStatus(value=HttpStatus.CONFLICT,
                  reason="Data integrity violation")  // 409
  @ExceptionHandler(DataIntegrityViolationException.class)
  public void conflict() {
    // 啥也不干
  }
  
  // 指定view来渲染对应的异常
  @ExceptionHandler({SQLException.class,DataAccessException.class})
  public String databaseError() {
    // Nothing to do.  Returns the logical view name of an error page, passed
    // to the view-resolver(s) in usual way.
    // Note that the exception is NOT available to this view (it is not added
    // to the model) but see "Extending ExceptionHandlerExceptionResolver"
    // below.
    // 啥也不干,就返回异常页面view的名称
    // 注意这里的view访问不到异常,因为异常没有添加到model中
    return "databaseError";
  }
  // 拦截该Controller抛出的所有异常,同时把异常信息通过ModelAndView传给视图
  // 或者你可以继承ExceptionHandlerExceptionResolver来实现,见下文
  @ExceptionHandler(Exception.class)
  public ModelAndView handleError(HttpServletRequest req, Exception ex) {
    logger.error("Request: " + req.getRequestURL() + " raised " + ex);
    ModelAndView mav = new ModelAndView();
    mav.addObject("exception", ex);
    mav.addObject("url", req.getRequestURL());
    mav.setViewName("error");
    return mav;
  }
}

Обратите внимание, что использование@ExceptionHandlerОбязательно укажите, какое исключение обрабатывается, иначе будет сообщено об исключении:java.lang.IllegalArgumentException: No exception types mapped to {public java.lang.String XXController.exceptionHandler()}

Глобальное обращение с исключением

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

@ControllerAdviceЭто аннотация, добавленная Spring 3.2.Как и имя, эта аннотация предоставляет функцию расширения контроллера, которую можно использовать в классе совета.@ExceptionHandler,@InitBinder,@ModelAttributeАннотированные методы применяются ко всем контроллерам. Наиболее часто используется@ExceptionHandler. Изначально нам нужно определить в каждом из контроллеров@ExceptionHandlerТеперь мы можем объявить@ControllerAdviceкласс, затем определите единый@ExceptionHandlerметод.

Например, в приведенном выше примере используйте@ControllerAdviceзаписывается следующим образом:

@ControllerAdvice
class GlobalControllerExceptionHandler {
    @ResponseStatus(HttpStatus.CONFLICT)  // 409
    @ExceptionHandler(DataIntegrityViolationException.class)
    public void handleConflict() {
        // 啥也不干
    }
}

Если вы хотите перехватить все ошибки, это то же самое, что и в приведенном выше примере уровня контроллера, установите перехваченное исключение какException.classВот и все.

@ControllerAdvice
class GlobalDefaultExceptionHandler {
  public static final String DEFAULT_ERROR_VIEW = "error";
  @ExceptionHandler(value = Exception.class)
  public ModelAndView
  defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
    // 这里需要注意一下,因为这个方法会拦截所有异常,包括设置了@ResponseStatus注解的异常,如果你不想拦截这些异常,可以过滤一下,然后重新抛出
    if (AnnotationUtils.findAnnotation
                (e.getClass(), ResponseStatus.class) != null)
      throw e;
    // 组装异常信息给视图
    ModelAndView mav = new ModelAndView();
    mav.addObject("exception", e);
    mav.addObject("url", req.getRequestURL());
    mav.setViewName(DEFAULT_ERROR_VIEW);
    return mav;
  }
}

более глубокий перехват

Перехваты уровня Controller и Controller-Advice, упомянутые выше, основаны на аннотациях и являются расширенными функциями. В базовой реализации Spring используетHandlerExceptionResolver.

Все определено вDispatcherServletКомпонент в контексте приложения, если он реализованHandlerExceptionResolverИнтерфейс будет использоваться для обработки перехвата исключений.

Взгляните на определение интерфейса:

public interface HandlerExceptionResolver {
    ModelAndView resolveException(HttpServletRequest request, 
            HttpServletResponse response, Object handler, Exception ex);
}

handlerПараметр является ссылкой на контроллер, вызвавший исключение.

Spring реализует несколькоHandlerExceptionResolver, эти классы являются основой для нескольких функций, упомянутых выше:

  • ExceptionHandlerExceptionResolver: определить, можно ли сопоставить исключение с соответствующим контроллером или советом контроллера.@ExceptionHandlerметод, если он может быть запущен (упомянутая выше функция метода перехвата исключений реализована этим классом)
  • ResponseStatusExceptionResolver: определить, является ли исключение@ResponseStatusАннотация, если да, используйте информацию аннотации для обновления ответа (упомянутый выше настраиваемый код состояния HTTP реализован с этой функцией)
  • DefaultHandlerExceptionResolver: преобразование исключений Spring в коды состояния HTTP (используемые внутри Spring).

эти несколькоHandlerExceptionResolverОн будет выполняться в таком порядке, то есть по цепочке обработки исключений.

Здесь видно,resolveExceptionсигнатура метода не имеетModelпараметры, поэтому@ExceptionHandlerМетод не может внедрить этот параметр, поэтому в приведенном выше методе перехвата исключений можно только создать новую модель.

Так что, если вам нужно, вы можете унаследовать егоHandlerExceptionResolverреализовать собственную цепочку обработки исключений. а затем реализоватьOrderedинтерфейс, чтобы можно было контролировать порядок выполнения процессоров.

SimpleMappingExceptionResolver

Spring обеспечивает очень удобный в использованииHandlerExceptionResolver,ВызовSimpleMappingExceptionResolver. Имеет множество полезных функций:

  • Сопоставьте имена исключений с именами представлений (имена исключений должны указывать только имя класса, а не имя пакета)
  • Укажите страницу ошибки по умолчанию
  • распечатать исключение в журнал
  • Укажите исключение для имени свойства в представлении, имя свойства по умолчанию — исключение. (@ExceptionHandlerПредставление, указанное методом, не может получить исключение по умолчанию, иSimpleMappingExceptionResolverУказанный вид может)

Использование заключается в следующем:

<bean id="simpleMappingExceptionResolver" class=
    "org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <map>
            <entry key="DatabaseException" value="databaseError"/>
            <entry key="InvalidCreditCardException" value="creditCardError"/>
        </map>
    </property>
    <!-- See note below on how this interacts with Spring Boot -->
    <property name="defaultErrorView" value="error"/>
    <property name="exceptionAttribute" value="ex"/>
        
    <!-- Name of logger to use to log exceptions. Unset by default, 
            so logging is disabled unless you set a value. -->
    <property name="warnLogCategory" value="example.MvcLogger"/>
</bean>

Конфигурация Java:

@Configuration
@EnableWebMvc  // Optionally setup Spring MVC defaults (if you aren't using
               // Spring Boot & haven't specified @EnableWebMvc elsewhere)
public class MvcConfiguration extends WebMvcConfigurerAdapter {
  @Bean(name="simpleMappingExceptionResolver")
  public SimpleMappingExceptionResolver
                  createSimpleMappingExceptionResolver() {
    SimpleMappingExceptionResolver r =
                new SimpleMappingExceptionResolver();
    Properties mappings = new Properties();
    mappings.setProperty("DatabaseException", "databaseError");
    mappings.setProperty("InvalidCreditCardException", "creditCardError");
    r.setExceptionMappings(mappings);  // None by default
    r.setDefaultErrorView("error");    // No default
    r.setExceptionAttribute("ex");     // Default is "exception"
    r.setWarnLogCategory("example.MvcLogger");     // No default
    return r;
  }
  ...
}

Пожалуй, самое полезное здесьdefaultErrorViewТеперь его можно использовать для настройки страницы ошибок по умолчанию.

самоунаследованныйSimpleMappingExceptionResolverТакже очень распространено расширение функциональности

  • Унаследованные классы могут устанавливать конфигурацию по умолчанию в конструкторе.
  • покрытиеbuildLogMessageМетод настройки информации журнала, фиксированный возврат по умолчанию: выполнение обработчика привело к исключению
  • покрытиеdoResolveExceptionметод, вы можете передать больше информации, которая вам нужна, в журнал ошибок

Примеры следующие:

public class MyMappingExceptionResolver extends SimpleMappingExceptionResolver {
  public MyMappingExceptionResolver() {
    // 默认启用日志
    setWarnLogCategory(MyMappingExceptionResolver.class.getName());
  }
  @Override
  public String buildLogMessage(Exception e, HttpServletRequest req) {
    return "MVC exception: " + e.getLocalizedMessage();
  }
    
  @Override
  protected ModelAndView doResolveException(HttpServletRequest req,
        HttpServletResponse resp, Object handler, Exception ex) {
    // 调用父类飞方法来获得ModelAndView
    ModelAndView mav = super.doResolveException(req, resp, handler, ex);
        
    // 添加额外的字段给视图
    mav.addObject("url", request.getRequestURL());
    return mav;
  }
}

Обработка исключений REST

В стиле REST возвращаемое сообщение об ошибке представляет собой json вместо страницы, как это сделать? Особенно просто, определите класс, который возвращает информацию:

public class ErrorInfo {
    public final String url;
    public final String ex;
    public ErrorInfo(String url, Exception ex) {
        this.url = url;
        this.ex = ex.getLocalizedMessage();
    }
}

Затем добавьте обработчик ошибок@ResponseBodyПросто делать:

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MyBadDataException.class)
@ResponseBody ErrorInfo
handleBadRequest(HttpServletRequest req, Exception ex) {
    return new ErrorInfo(req.getRequestURL(), ex);
}

Когда вы используете спецэффекты?

Весна предоставляет нам много вариантов, как мы выбираем?

  • Если исключение объявлено вами, рассмотрите возможность использования@ResponseStatusаннотация
  • Можно использовать другие исключения@ControllerAdviceсередина@ExceptionHandlerметод или использованиеSimpleMappingExceptionResolver
  • Если контроллеру необходимо настроить исключение, его можно добавить в контроллер.@ExceptionHandlerметод.

Если вы смешиваете эти функции, вы должны обратить внимание на то, что в контроллере@ExceptionHandlerкоэффициент приоритета метода@ControllerAdviceсередина@ExceptionHandlerметод высокий, а если их несколько@ControllerAdviceкласс, порядок выполнения не определен.

использованная литература