在項目中,常常會使用ExceptionHandler來做爲全局性的異常處理中心。那麼ExceptionHandler處理異常的原理是什麼呢,今天就來分析一下。web
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = RuntimeException.class)
public String handle(){
return "error";
}
}
複製代碼
使用仍是很簡單的,在類上面添加ControllerAdvice註解,在方法上面添加ExceptionHandler註解,就能夠在方法裏處理相應的異常信息了。bash
異常處理的核心類是ExceptionHandlerExceptionResolver,進入該類。查看afterPropertiesSet方法。app
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中。ide
看過了ControllerAdvice和ExceptionHandler註解的做用後,咱們來看一下異常處理的原理。進入DispatcherServlet的doDispatch方法post
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方法中進行處理。因此咱們進入該方法一探究竟。ui
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是哪裏來的呢?this
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方法。因此咱們進入該方法。spa
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層參數的封裝 是同樣的,可是他們能處理的參數類型卻不同。debug
查看ExceptionHandlerExceptionResolver類的afterPropertiesSet方法:3d
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那邊的類型少了許多,使用的時候注意一下便可。