I have a simple Spring Boot 2.1 application with a Spring Interceptor and @RestControllerAdvice
.
My requirement is to have the Spring Interceptor be called for all situations, including when an exception occurs.
For custom exceptions, the Interceptor handler methods do get called, e.g. preHandle()
and afterCompletion()
. However, for exceptions handled by ResponseEntityExceptionHandler
, the Spring Interceptor does not get called (I need ResponseEntityExceptionHandler
's methods to create a custom ResponseBody
to send back, however, I also need to trigger Interceptor's afterCompletion()
for auditing purposes).
For instance, if a REST request is made with PATCH
HTTP method, it only executes PersonControllerExceptionHandler.handleHttpRequestMethodNotSupported()
and no PersonInterceptor
is invoked.
Exception Handler:
@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);
}
}
The Interceptor:
@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);
}
}
}
Registering the Interceptor:
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new PersonInterceptor()).addPathPatterns("/person/*");
}
}
Controller:
@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);
}
}
Initially I thought it has something to do with @Ordered
but trying various scenarios where I give PersonInterceptor
a higher precedence than @RestControllerAdvice
yields the same undesirable outcome (or vice versa).
After digging into Spring framework, it seems like if a handler is not found, an exception is thrown back to DispacherServlet#doDispatch()
which goes into a catch
block, and therefore, it skips interceptor mapping process, including the afterCompletion()
(I'm using Spring 5.1
. as an example to trace the execution path):
DispacherServlet#doDispatch()
is called and attempts is made to get theHandlerExecutionChain
- I can see there are several
HandlerMapping
's; the one that fails isRequestMappingHandlerMapping
- In
AbstractHandlerMapping#getHandler()
, it tries to get the handler viaAbstractHandlerMethodMapping#getHandlerInternal()
- Eventually,
AbstractHandlerMethodMapping#lookupHandlerMethod()
is called which fails to find a matching pattern due to the fact that there is noPATCH getPerson()
, but ratherGET getPerson()
- At this point,
RequestMappingInfoHandlerMapping#handleNoMatch()
throwsHttpRequestMethodNotSupportedException
- This exception bubbles up to
DispatcherServlet#doDispatch()
exception clause which then processes by the exception resolver that it finds inDispatcherServlet#processHandlerException()
(of course, this finds an exception resolver and doesn't throw an exception which might triggerDispatcherServlet#triggerAfterCompletion()
when an exception is caught inDispacherServlet#doDispatch()
exception clause
Is there something I am missing to trigger the interceptor's afterCompletion()
in cases when there is no handler match?