In projects, ExceptionHandler is often used as a global exception handling center. So what is the principle of ExceptionHandler handling exceptions, today to analyze it.
ExceptionHandler Example
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = RuntimeException.class)
public String handle() {return "error"; }}Copy the code
It is easy to use. Add the ControllerAdvice annotation to the class and the ExceptionHandler annotation to the method to handle the corresponding exception message in the method.
The principle of analyzing
The role of ControllerAdvice and ExceptionHandler annotations
Exception handling core classes is ExceptionHandlerExceptionResolver, enter the class. Look at the afterPropertiesSet method.
public void afterPropertiesSet() { // Do this first, it may add ResponseBodyAdvice beans initExceptionHandlerAdviceCache(); . } private voidinitExceptionHandlerAdviceCache() {
if (getApplicationContext() == null) {
return; } // This line of code will find all the classes marked with ControllerAdvice annotations List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext()); AnnotationAwareOrderComparator.sort(adviceBeans);for(ControllerAdviceBean adviceBean : adviceBeans) { Class<? > beanType = adviceBean.getBeanType();if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: "+ adviceBean); } // Iterate through these classes to find the methods marked with the ExceptionHandler annotation. ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);if (resolver.hasExceptionMappings()) {
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}
if(ResponseBodyAdvice.class.isAssignableFrom(beanType)) { this.responseBodyAdvice.add(adviceBean); }}... }Copy the code
Through the above code, you can see that in ExceptionHandlerExceptionResolver class, the class to scan all labeled ExceptionHandler annotation method, and bring them into the exceptionHandlerAdviceCache.
Principle of exception handling
Having seen the ControllerAdvice and ExceptionHandler annotations in action, let’s look at how exception handling works. Enter the doDispatch method of the DispatcherServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { ... // Handle the controller method mv = ha.handle(processedRequest, response, mappedHandler.gethandler ()); . } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { dispatchException = new NestedServletException("Handler dispatch failed", err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); }... }Copy the code
It can be seen from the doDispatch method that the program first processes the business logic of the Controller layer. For the exceptions thrown by the business logic, the program is uniformly encapsulated and then processed in the processDispatchResult method. So we went into the method to find out.
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false; // If an exception occurs in the program, handle itif(exception ! = null) {if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
...
}
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
if(this.handlerExceptionResolvers ! = null) {/ / traverse handlerExceptionResolvers exception information processingfor (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
exMv = resolver.resolveException(request, response, handler, ex);
if(exMv ! = null) {break; }}}... }Copy the code
Where did the handlerExceptionResolvers here?
private void initHandlerExceptionResolvers(ApplicationContext context) {
this.handlerExceptionResolvers = null;
if (this.detectAllHandlerExceptionResolvers) {
// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true.false); . }... }Copy the code
When the DispatcherServlet is initialized, it looks for a class of type HandlerExceptionResolver in the container. And just ExceptionHandlerExceptionResolver class is inherited HandlerExceptionResolver interface, so this place will put him in the DispatcherServlet. So the above traverse handlerExceptionResolvers processing abnormal information, is to call the ExceptionHandlerExceptionResolver resolveException method. So let’s go into that method.
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
if (shouldApplyTo(request, handler)) {
prepareResponse(ex, response);
ModelAndView result = doResolveException(request, response, handler, ex); . } } protected final ModelAndViewdoResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex);
}
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) { ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception); .else{ // Otherwise, just the given exception as-is exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod); }}... } public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { ObjectreturnValue = invokeForRequest(webRequest, mavContainer, providedArgs); . } public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... ProvidedArgs) throws Exception {// Obtain method parameters Object[] args = getMethodArgumentValues(Request, mavContainer, providedArgs);if (logger.isTraceEnabled()) {
logger.trace("Arguments: "+ Arrays.toString(args)); } // Execute methodreturn doInvoke(args);
}
Copy the code
The entire exception execution logic as above code, simple point is to find the corresponding exception handling method, execute him. The logic in getMethodArgumentValues is the same as that in SpringBoot’s source-parsed controller layer, but the types of arguments they can handle are different.
Check the afterPropertiesSet ExceptionHandlerExceptionResolver class methods:
public void afterPropertiesSet() {...if(this.argumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); }... } protected List<HandlerMethodArgumentResolver>getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
// Annotation-based argument resolution
resolvers.add(new SessionAttributeMethodArgumentResolver());
resolvers.add(new RequestAttributeMethodArgumentResolver());
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
// Custom arguments
if(getCustomArgumentResolvers() ! = null) { resolvers.addAll(getCustomArgumentResolvers()); }return resolvers;
}
Copy the code
This is the type of argument that is accepted in the ExceptionHandler method. Take a look, it is much less than the controller side of the type, pay attention to use it.