Как вызвать перехватчик Spring MVC, если совпадение обработчика не найдено - PullRequest
0 голосов
/ 20 января 2020

У меня есть простое приложение 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. в качестве примера для отслеживания пути выполнения):

  1. DispacherServlet#doDispatch() вызывается и делается попытка получить HandlerExecutionChain
  2. , который я вижу там несколько HandlerMapping х; тот, который терпит неудачу, является RequestMappingHandlerMapping
  3. В AbstractHandlerMapping#getHandler(), он пытается получить обработчик через AbstractHandlerMethodMapping#getHandlerInternal()
  4. В конце концов, AbstractHandlerMethodMapping#lookupHandlerMethod(), который не может найти соответствующий шаблон из-за того, что нет PATCH getPerson(), а GET getPerson()
  5. В этот момент RequestMappingInfoHandlerMapping#handleNoMatch() выбрасывает HttpRequestMethodNotSupportedException
  6. Это исключение всплывает до DispatcherServlet#doDispatch() условия исключения, которое затем обрабатывается обработчиком исключений так, что оно находит в DispatcherServlet#processHandlerException() (конечно, это находит решатель исключений и не генерирует исключение, которое может вызвать DispatcherServlet#triggerAfterCompletion(), когда исключение поймано в DispacherServlet#doDispatch() исключительная оговорка

Есть ли что-то, чего мне не хватает, чтобы вызвать перехватчик afterCompletion() в случаях, когда нет совпадения обработчика?

...