SpringBoot原始碼解析-ExceptionHandler處理異常的原理
在專案中,經常會使用ExceptionHandler來作為全域性性的異常處理中心。那麼ExceptionHandler處理異常的原理是什麼呢,今天就來分析一下。
ExceptionHandler使用示例
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(value = RuntimeException.class) public String handle(){ return "error"; } } 複製程式碼
使用還是很簡單的,在類上面新增ControllerAdvice註解,在方法上面新增ExceptionHandler註解,就可以在方法裡處理相應的異常資訊了。
原理剖析
ControllerAdvice和ExceptionHandler註解的作用
異常處理的核心類是ExceptionHandlerExceptionResolver,進入該類。檢視afterPropertiesSet方法。
public void afterPropertiesSet() { // Do this first, it may add ResponseBodyAdvice beans initExceptionHandlerAdviceCache(); ... } private void initExceptionHandlerAdviceCache() { if (getApplicationContext() == null) { return; } //這行程式碼會找出所有標記了ControllerAdvice註解的類 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); } //遍歷這些類,找出有ExceptionHandler註解標註的方法。 ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType); if (resolver.hasExceptionMappings()) { this.exceptionHandlerAdviceCache.put(adviceBean, resolver); } if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) { this.responseBodyAdvice.add(adviceBean); } } ... } 複製程式碼
通過上述程式碼可以看出,在ExceptionHandlerExceptionResolver類中,該類掃描了所有標註有ExceptionHandler註解的方法,並將他們存入了exceptionHandlerAdviceCache中。
異常處理的原理
看過了ControllerAdvice和ExceptionHandler註解的作用後,我們來看一下異常處理的原理。進入DispatcherServlet的doDispatch方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { ... //處理controller方法 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); } ... } 複製程式碼
從doDispatch方法中可以看出,程式先處理了controller層的業務邏輯,對於業務邏輯丟擲的異常,程式統一做了封裝,然後進入了processDispatchResult方法中進行處理。所以我們進入該方法一探究竟。
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { boolean errorView = false; //如果程式發生了異常,就進行處理 if (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) { //遍歷handlerExceptionResolvers處理異常資訊 for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) { exMv = resolver.resolveException(request, response, handler, ex); if (exMv != null) { break; } } } ... } 複製程式碼
那這邊的handlerExceptionResolvers是哪裡來的呢?
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); ... } ... } 複製程式碼
在DispatcherServlet初始化的時候,會去容器中找HandlerExceptionResolver型別的類。而剛剛的ExceptionHandlerExceptionResolver類就是繼承了HandlerExceptionResolver介面,所以這個地方就將他放入了DispatcherServlet中。所以上面的遍歷handlerExceptionResolvers處理異常資訊的地方,就是呼叫了ExceptionHandlerExceptionResolver的resolveException方法。所以我們進入該方法。
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 ModelAndView doResolveException( 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 { Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); ... } public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //獲取方法的引數 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Arguments: " + Arrays.toString(args)); } //執行方法 return doInvoke(args); } 複製程式碼
整個異常的執行邏輯如上面的程式碼,簡單點說就是找到相應的異常處理方法,執行他。這個地方getMethodArgumentValues裡面的邏輯和SpringBoot原始碼解析-controller層引數的封裝 是一樣的,但是他們能處理的引數型別卻不一樣。
檢視ExceptionHandlerExceptionResolver類的afterPropertiesSet方法:
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; } 複製程式碼
這邊就是ExceptionHandler方法中可以接收的引數型別了。看一下,要比controller那邊的型別少了許多,使用的時候注意一下即可。