SpringMVC異常解析器的原理是什么

本篇內(nèi)容主要講解“Spring MVC異常解析器的原理是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“Spring MVC異常解析器的原理是什么”吧!

成都創(chuàng)新互聯(lián)公司長期為成百上千客戶提供的網(wǎng)站建設(shè)服務(wù),團隊從業(yè)經(jīng)驗10年,關(guān)注不同地域、不同群體,并針對不同對象提供差異化的產(chǎn)品和服務(wù);打造開放共贏平臺,與合作伙伴共同營造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為宏偉企業(yè)提供專業(yè)的成都做網(wǎng)站、網(wǎng)站制作,宏偉網(wǎng)站改版等技術(shù)服務(wù)。擁有十年豐富建站經(jīng)驗和眾多成功案例,為您定制開發(fā)。

使用介紹

一般自定義異常處理策略有兩種方式

  1. 使用@ExceptionHandler注解

  2. 實現(xiàn)HandlerExceptionResolver接口

因為@ExceptionHandler注解的方式已經(jīng)足夠強大,所以我們一般也很少通過實現(xiàn)HandlerExceptionResolver來自定義異常處理策略。

簡單介紹一下@ExceptionHandler的使用,后面會結(jié)合這些例子進行源碼分析

@RestController @RequestMapping("location") public class LocationController {   @RequestMapping("getLocationInfo")  public String index() {   int sum = 10 / 0;   return "locationInfo";  }   @ExceptionHandler(RuntimeException.class)  public String processRuntimeException() {   return "LocationController -> 發(fā)生RuntimeException";  }   @ExceptionHandler(Exception.class)  public String processException() {   return "LocationController -> 發(fā)生Exception";  } }

訪問如下鏈接,返回結(jié)果為

http://localhost:8080/location/getLocationInfo LocationController -> 發(fā)生RuntimeException

把processRuntimeException方法注釋掉以后,再次訪問上面的鏈接,結(jié)果為

LocationController -> 發(fā)生Exception

如果在每個Controller里面都寫異常解析器還是很麻煩的,能不能在一個地方統(tǒng)一處理異常呢?當(dāng)然可以,這時候就不得不用到@RestControllerAdvice或者@ControllerAdvice

寫如下的全局異常解析器

@RestControllerAdvice public class MyExceptionHandler {   @ExceptionHandler(RuntimeException.class)  public String processRuntimeException() {   return "MyExceptionHandler -> 發(fā)生RuntimeException";  }   @ExceptionHandler(Exception.class)  public String processException() {   return "MyExceptionHandler -> 發(fā)生RuntimeException";  } }

訪問上面的鏈接,返回結(jié)果為

LocationController -> 發(fā)生Exception

我們把LocationController類的processException方法也注釋掉,此時LocationController類里面已經(jīng)沒有被@ExceptionHandler注解標記的方法了

訪問上面的鏈接,返回結(jié)果為

MyExceptionHandler -> 發(fā)生RuntimeException

把MyExceptionHandler中的processRuntimeException方法注釋掉訪問上面的鏈接,返回結(jié)果為

MyExceptionHandler -> 發(fā)生Exception

通過以上的例子,我們可以得出如下結(jié)論

  1. @RestControllerAdvice或者@ControllerAdvice類內(nèi)的解析器的優(yōu)先級低于@RequestMapping類的解析器的優(yōu)先級

  2. 如果一個異常能被多個解析器所處理,則選擇繼承關(guān)系最近的解析器

假設(shè)BizException繼承自NullPointException A方法解析BizException B方法解析NullPointException  C方法解析Exception

BizException會被A方法解析 NullPointException會被B方法解析  如果沒有A方法,則BizException會被B方法解析,如果B方法也沒有,則被C方法解析,不難理解哈

@RestControllerAdvice和@ControllerAdvice有什么區(qū)別呢?

名字上就可以猜出@RestControllerAdvice只是在@ControllerAdvice的基礎(chǔ)上加了@ResponseBody注解,看一波源碼也確實如此。所以@RestControllerAdvice類最終返回的是JSON,@ControllerAdvice最終返回的是視圖。如果你不明白為什么加了@ResponseBody注解最終返回的內(nèi)容為JSON,建議看一下返回值處理器相關(guān)的內(nèi)容

源碼分析

異常解析器接口定義如下

public interface HandlerExceptionResolver {   // 將異常封裝為ModelAndView后返回  @Nullable  ModelAndView resolveException(    HttpServletRequest request, HttpServletResponse response,     @Nullable Object handler, Exception ex);  }

Spring MVC默認的異常解析器存放在如下屬性中

@Nullable private List<HandlerExceptionResolver> handlerExceptionResolvers;

Spring MVC異常解析器的原理是什么

順序依次為

  • ExceptionHandlerExceptionResolver

  • ResponseStatusExceptionResolver

  • DefaultHandlerExceptionResolver

UML圖如下

Spring MVC異常解析器的原理是什么

Order接口是用來排序的哈,Spring  MVC默認的解析器不是通過Order接口來控制順序的,因為默認的解析器都繼承自AbstractHandlerExceptionResolver,并且都沒有重寫getOrder方法

對Spring  MVC比較清楚的小伙伴應(yīng)該都知道DispatcherServlet屬性的默認實現(xiàn)都定義在源碼包的DispatcherServlet.properties文件中,List的順序也是按這個來的。放一部分內(nèi)容

org.springframework.web.servlet.HandlerAdapter=     org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\  org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\  org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter  org.springframework.web.servlet.HandlerExceptionResolver=     org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\  org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\  org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionReso

接下來分析這3個默認的HandlerExceptionResolver

ExceptionHandlerExceptionResolver

ExceptionHandlerExceptionResolver用于支持@ExceptionHandler,而@ExceptionHandler應(yīng)該是我們最常的,方便我們自定義異常處理策略,比通過實現(xiàn)HandlerExceptionResolver接口的方式簡單

從AbstractHandlerMethodExceptionResolver#shouldApplyTo可以看到

@Override protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {  if (handler == null) {   // handler為空,交給父類去判斷   // 默認該邏輯返回true   return super.shouldApplyTo(request, null);  }  else if (handler instanceof HandlerMethod) {   HandlerMethod handlerMethod = (HandlerMethod) handler;   handler = handlerMethod.getBean();   // 交給父類判斷   return super.shouldApplyTo(request, handler);  }  else {   // 不支持   return false;  } }

只有當(dāng)handler為空或者handler的類型為HandlerMethod時(@RequestMapping返回的類型為HandlerMethod)才會執(zhí)行后面的異常解析邏輯。所以你通過實現(xiàn)Controller接口或者實現(xiàn)HttpRequestHandler接口定義的Handler,這個注解是不起作用的

@ExceptionHandler的處理過程主要和下面2個類有關(guān)系ExceptionHandlerExceptionResolver,ExceptionHandlerMethodResolver

用幾個成員變量說一下處理過程,就不貼過多的代碼了

ExceptionHandlerExceptionResolver

// 省略了繼承和實現(xiàn)關(guān)系 public class ExceptionHandlerExceptionResolver {   @Nullable  private HandlerMethodArgumentResolverComposite argumentResolvers;   @Nullable  private HandlerMethodReturnValueHandlerComposite returnValueHandlers;   private List<HttpMessageConverter<?>> messageConverters;    // 被@RequestMapping標記的類 -> ExceptionHandlerMethodResolver  private final Map<Class<?>, ExceptionHandlerMethodResolver>   exceptionHandlerCache = new ConcurrentHashMap<>(64);   // 被@ControllerAdvice注解標記的類 -> ExceptionHandlerMethodResolver  private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver>  exceptionHandlerAdviceCache = new LinkedHashMap<>(); }

可以看到ExceptionHandlerExceptionResolver類定義了自己的參數(shù)處理器,返回值處理器,消息轉(zhuǎn)換器。所以你可以通過這些組件反向知道@ExceptionHandler方法支持的參數(shù)類型

例如從如下方法可以知道,支持的參數(shù)類型為@SessionAttribute,@RequestAttribute等  如果你寫個@RequestParam是肯定不會注入進來的

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; }

最重要的4個map來了,ExceptionHandlerExceptionResolver的工作過程主要就是操作這4個map

// 省略了繼承和實現(xiàn)關(guān)系 public class ExceptionHandlerExceptionResolver {   // 被@RequestMapping標記的類 -> ExceptionHandlerMethodResolver  private final Map<Class<?>, ExceptionHandlerMethodResolver>  exceptionHandlerCache = new ConcurrentHashMap<>(64);   // 被@ControllerAdvice注解標記的類 -> ExceptionHandlerMethodResolver  private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver>  exceptionHandlerAdviceCache = new LinkedHashMap<>();     }

exceptionHandlerCache保存了@RequestMapping對應(yīng)的ExceptionHandlerMethodResolver,是在執(zhí)行異常解析的過程中被賦值的

exceptionHandlerAdviceCache保存了@ControllerAdvice對應(yīng)的  ExceptionHandlerMethodResolver,是在ExceptionHandlerExceptionResolver被初始化的過程中賦值的

而ExceptionHandlerMethodResolver你可以認為只是封裝了一下Exception及其對應(yīng)的Method

以最開始的例子演示,ExceptionHandlerExceptionResolver初始化后

Spring MVC異常解析器的原理是什么

此時exceptionHandlerCache是沒有值的  訪問如下鏈接后

http://localhost:8080/location/getLocationInfo

exceptionHandlerCache中的值如下,LocationController及其對應(yīng)的ExceptionHandlerMethodResolver被放了進來追一下以下方法的執(zhí)行  ExceptionHandlerExceptionResolver#doResolveHandlerMethodException  ExceptionHandlerExceptionResolver#getExceptionHandlerMethod

可以得出我們測試的結(jié)論@RestControllerAdvice或者@ControllerAdvice類內(nèi)的解析器的優(yōu)先級低于@RequestMapping類的解析器的優(yōu)先級

總體實現(xiàn)也不難,從exceptionHandlerCache中能找到解析器就返回執(zhí)行,找不到就從exceptionHandlerAdviceCache中找,這不是就實現(xiàn)了優(yōu)先級了嗎?

接著來看剩下的2個Map

public class ExceptionHandlerMethodResolver {    // 異常 -> 對應(yīng)的處理方法  private final Map<Class<? extends Throwable>, Method>  mappedMethods = new HashMap<>(16);   // 異常 -> 對應(yīng)的處理方法  // 這個是基于mappedMethods又做了一次緩存  // 為什么要再做一次緩存呢?  // 是因為根據(jù)異常類型獲取處理方法的時候,一個異??赡苡卸鄠€處理方法,即一個異常會從mappedMethods中查出多個處理方法  // 最后返回的是繼承關(guān)系最近的異常對應(yīng)的處理方法,所以在查找的時候又做了一次緩存,避免每次查mappedMethods然后取最優(yōu)值  // 從exceptionLookupCache中就可以直接查到最優(yōu)的處理方法  private final Map<Class<? extends Throwable>, Method>  exceptionLookupCache = new ConcurrentReferenceHashMap<>(16);   }

@ControllerAdvice的mappedMethods是在ExceptionHandlerExceptionResolver初始化的過程中賦值的

Spring MVC異常解析器的原理是什么

@RequestMapping的mappedMethods是在執(zhí)行異常解析的過程中被賦值的

Spring MVC異常解析器的原理是什么

而exceptionLookupCache是在異常解析過程中,通過Exception查找Method的過程中基于mappedMethods做的緩存

為什么在查找過程中要再做一次緩存呢?

是因為根據(jù)異常類型獲取處理方法的時候,一個異??赡苡卸鄠€處理方法,即一個異常會從mappedMethods中查出多個處理方法,最后返回的是繼承關(guān)系最近的異常對應(yīng)的處理方法,所以在查找的時候又做了一次緩存,避免每次查mappedMethods然后取最優(yōu)值。從exceptionLookupCache中就可以直接查到最優(yōu)的處理方法

以LocationController為例,查找一次異常后,exceptionLookupCache的值如下

Spring MVC異常解析器的原理是什么

這樣當(dāng)再次發(fā)生ArithmeticException異常時就能從exceptionLookupCache找到對應(yīng)的處理方法

ResponseStatusExceptionResolver

ResponseStatusExceptionResolver和DefaultHandlerExceptionResolver的實現(xiàn)都不是很難,就不進行過多的分析了

ResponseStatusExceptionResolver主要用來處理如下異常

拋出的異常類型繼承自ResponseStatusException

拋出的異常類型被@ResponseStatus標記

以一個例子來演示這個處理器的功能

@ResponseStatus(HttpStatus.UNAUTHORIZED) public class UnauthorizedException extends RuntimeException { } @RestController @RequestMapping("shoppingCar") public class ShoppingCarController {   @RequestMapping("getCarInfo")  public String index() {   throw new UnauthorizedException();  } }

訪問

http://localhost:8080/shoppingCar/getCarInfo

顯示如下

Spring MVC異常解析器的原理是什么

DefaultHandlerExceptionResolver

用來處理一些常見的Http異常,如

400:請求無效 405:請求方法不支持 500:內(nèi)部服務(wù)器錯誤

執(zhí)行入口

# DispatcherServlet#processDispatchResult的部分方法 // 處理過程發(fā)生了異常 if (exception != null) {  if (exception instanceof ModelAndViewDefiningException) {   logger.debug("ModelAndViewDefiningException encountered", exception);   // 直接使用異常中封裝的ModelAndView作為最終的ModelAndView結(jié)果   mv = ((ModelAndViewDefiningException) exception).getModelAndView();  }  else {   // 其他異常類型,先獲取解析器   Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);   // 通過異常解析器將異常解析為一個錯誤視圖   mv = processHandlerException(request, response, handler, exception);   errorView = (mv != null);  } }

如果整個處理過程發(fā)生異常,依次調(diào)用DispatcherServlet的成員變量handlerExceptionResolvers的resolveException方法,找到第一個不為null的ModelAndView,然后返回

@Nullable private List<HandlerExceptionResolver> handlerExceptionResolvers;

到此,相信大家對“Spring MVC異常解析器的原理是什么”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

新聞標題:SpringMVC異常解析器的原理是什么
分享地址:http://bm7419.com/article8/gejcip.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站排名、網(wǎng)站設(shè)計公司、動態(tài)網(wǎng)站定制開發(fā)、移動網(wǎng)站建設(shè)、用戶體驗

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

h5響應(yīng)式網(wǎng)站建設(shè)