У меня есть простое приложение Spring Boot 2.1 с Spring Interceptor и @RestControllerAdvice
.
Мое требование состоит в том, чтобы Spring Interceptor вызывался во всех ситуациях, в том числе при возникновении исключения.
Для пользовательских исключений методы-обработчики Interceptor вызывают, например, preHandle()
и afterCompletion()
. Однако, для исключений, обработанных ResponseEntityExceptionHandler
, , Spring Interceptor не вызывается (мне нужны ResponseEntityExceptionHandler
методы для создания пользовательского ResponseBody
для отправки обратно, однако мне также нужно вызвать Перехватчик afterCompletion()
для целей аудита) .
Например, , если запрос REST выполняется с помощью метода PATCH
HTTP, он выполняет только PersonControllerExceptionHandler.handleHttpRequestMethodNotSupported()
и no PersonInterceptor
вызывается.
Обработчик исключений :
@RestControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class PersonControllerExceptionHandler extends ResponseEntityExceptionHandler {
private final static Logger LOGGER = LoggerFactory.getLogger(PersonControllerExceptionHandler.class);
@ExceptionHandler(value = {PersonException.class })
public ResponseEntity<Object> handlePersonException(PersonException exception) {
LOGGER.info("Person exception occurred");
return new ResponseEntity<Object>(new Person("Bad Age", -1),
HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(value = {Exception.class })
public ResponseEntity<Object> handleException(Exception exception) {
LOGGER.info("Exception occurred");
return new ResponseEntity<Object>(new Person("Unknown Age", -100),
HttpStatus.INTERNAL_SERVER_ERROR);
}
@Override
public ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
LOGGER.info("handleHttpRequestMethodNotSupported()...");
return new ResponseEntity<>(new Person("Argh!", 900), HttpStatus.METHOD_NOT_ALLOWED);
}
}
Перехватчик :
@Order(Ordered.HIGHEST_PRECEDENCE)
public class PersonInterceptor extends HandlerInterceptorAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(PersonInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
LOGGER.info("PersonInterceptor#preHandler()...");
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
LOGGER.info("PersonInterceptor#postHandler()...");
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
LOGGER.info("PersonInterceptor#afterCompletion()...");
if (ex != null) {
LOGGER.error("afterCompletion(): An exception occurred", ex);
}
}
}
Регистрация перехватчика :
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new PersonInterceptor()).addPathPatterns("/person/*");
}
}
Контроллер :
@RestController
@RequestMapping("/")
public class PersonController {
private final static Logger LOGGER = LoggerFactory.getLogger(PersonController.class);
@Autowired
private PersonService personService;
@GetMapping(path = "/person/{age}", produces = MediaType.APPLICATION_JSON_VALUE)
public Person getPerson(@PathVariable("age") Integer age) throws PersonException {
LOGGER.info("Age: {}", age);
return personService.getPerson(age);
}
}
Сначала я думал, что это как-то связано с @Ordered
, но пробовал разные Сценарий ios, где я даю PersonInterceptor
более высокий приоритет, чем @RestControllerAdvice
, приводит к тому же нежелательному результату (или наоборот).
После копания в Spring Framework создается впечатление, что если не найден обработчик, исключение возвращается в DispacherServlet#doDispatch()
, которое входит в блок catch
, и, следовательно, оно пропускает отображение перехватчика процесс, в том числе afterCompletion()
(я использую Spring 5.1
. в качестве примера для отслеживания пути выполнения):
DispacherServlet#doDispatch()
вызывается и делается попытка получить HandlerExecutionChain
- , который я вижу там несколько
HandlerMapping
х; тот, который терпит неудачу, является RequestMappingHandlerMapping
- В
AbstractHandlerMapping#getHandler()
, он пытается получить обработчик через AbstractHandlerMethodMapping#getHandlerInternal()
- В конце концов,
AbstractHandlerMethodMapping#lookupHandlerMethod()
, который не может найти соответствующий шаблон из-за того, что нет PATCH getPerson()
, а GET getPerson()
- В этот момент
RequestMappingInfoHandlerMapping#handleNoMatch()
выбрасывает HttpRequestMethodNotSupportedException
- Это исключение всплывает до
DispatcherServlet#doDispatch()
условия исключения, которое затем обрабатывается обработчиком исключений так, что оно находит в DispatcherServlet#processHandlerException()
(конечно, это находит решатель исключений и не генерирует исключение, которое может вызвать DispatcherServlet#triggerAfterCompletion()
, когда исключение поймано в DispacherServlet#doDispatch()
исключительная оговорка
Есть ли что-то, чего мне не хватает, чтобы вызвать перехватчик afterCompletion()
в случаях, когда нет совпадения обработчика?