日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
SpringMVC異常解析器,原理就是這么簡(jiǎn)單

使用介紹

站在用戶的角度思考問(wèn)題,與客戶深入溝通,找到東蘭網(wǎng)站設(shè)計(jì)與東蘭網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗(yàn),讓設(shè)計(jì)與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個(gè)性化、用戶體驗(yàn)好的作品,建站類型包括:成都網(wǎng)站設(shè)計(jì)、成都做網(wǎng)站、外貿(mào)網(wǎng)站建設(shè)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣、域名申請(qǐng)、網(wǎng)站空間、企業(yè)郵箱。業(yè)務(wù)覆蓋東蘭地區(qū)。

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

  1. 使用@ExceptionHandler注解
  2. 實(shí)現(xiàn)HandlerExceptionResolver接口

因?yàn)锧ExceptionHandler注解的方式已經(jīng)足夠強(qiáng)大,所以我們一般也很少通過(guò)實(shí)現(xiàn)HandlerExceptionResolver來(lái)自定義異常處理策略。

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

 
 
 
 
  1. @RestController 
  2. @RequestMapping("location") 
  3. public class LocationController { 
  4.  
  5.  @RequestMapping("getLocationInfo") 
  6.  public String index() { 
  7.   int sum = 10 / 0; 
  8.   return "locationInfo"; 
  9.  } 
  10.  
  11.  @ExceptionHandler(RuntimeException.class) 
  12.  public String processRuntimeException() { 
  13.   return "LocationController -> 發(fā)生RuntimeException"; 
  14.  } 
  15.  
  16.  @ExceptionHandler(Exception.class) 
  17.  public String processException() { 
  18.   return "LocationController -> 發(fā)生Exception"; 
  19.  } 

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

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

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

 
 
 
 
  1. LocationController -> 發(fā)生Exception 

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

寫(xiě)如下的全局異常解析器

 
 
 
 
  1. @RestControllerAdvice 
  2. public class MyExceptionHandler { 
  3.  
  4.  @ExceptionHandler(RuntimeException.class) 
  5.  public String processRuntimeException() { 
  6.   return "MyExceptionHandler -> 發(fā)生RuntimeException"; 
  7.  } 
  8.  
  9.  @ExceptionHandler(Exception.class) 
  10.  public String processException() { 
  11.   return "MyExceptionHandler -> 發(fā)生RuntimeException"; 
  12.  } 

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

 
 
 
 
  1. LocationController -> 發(fā)生Exception 

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

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

 
 
 
 
  1. MyExceptionHandler -> 發(fā)生RuntimeException 

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

 
 
 
 
  1. MyExceptionHandler -> 發(fā)生Exception 

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

  1. @RestControllerAdvice或者@ControllerAdvice類內(nèi)的解析器的優(yōu)先級(jí)低于@RequestMapping類的解析器的優(yōu)先級(jí)
  2. 如果一個(gè)異常能被多個(gè)解析器所處理,則選擇繼承關(guān)系最近的解析器

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

BizException會(huì)被A方法解析 NullPointException會(huì)被B方法解析 如果沒(méi)有A方法,則BizException會(huì)被B方法解析,如果B方法也沒(méi)有,則被C方法解析,不難理解哈

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

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

源碼分析

異常解析器接口定義如下

 
 
 
 
  1. public interface HandlerExceptionResolver { 
  2.  
  3.  // 將異常封裝為ModelAndView后返回 
  4.  @Nullable 
  5.  ModelAndView resolveException( 
  6.    HttpServletRequest request, HttpServletResponse response,  
  7.    @Nullable Object handler, Exception ex); 
  8.  

Spring MVC默認(rèn)的異常解析器存放在如下屬性中

 
 
 
 
  1. @Nullable 
  2. private List handlerExceptionResolvers; 

順序依次為

  • ExceptionHandlerExceptionResolver
  • ResponseStatusExceptionResolver
  • DefaultHandlerExceptionResolver

UML圖如下

Order接口是用來(lái)排序的哈,Spring MVC默認(rèn)的解析器不是通過(guò)Order接口來(lái)控制順序的,因?yàn)槟J(rèn)的解析器都繼承自AbstractHandlerExceptionResolver,并且都沒(méi)有重寫(xiě)getOrder方法

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

 
 
 
 
  1. org.springframework.web.servlet.HandlerAdapter= 
  2.     org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\ 
  3.  org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ 
  4.  org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter 
  5.  
  6. org.springframework.web.servlet.HandlerExceptionResolver= 
  7.     org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\ 
  8.  org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ 
  9.  org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionReso 

接下來(lái)分析這3個(gè)默認(rèn)的HandlerExceptionResolver

ExceptionHandlerExceptionResolver

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

從AbstractHandlerMethodExceptionResolver#shouldApplyTo可以看到

 
 
 
 
  1. @Override 
  2. protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) { 
  3.  if (handler == null) { 
  4.   // handler為空,交給父類去判斷 
  5.   // 默認(rèn)該邏輯返回true 
  6.   return super.shouldApplyTo(request, null); 
  7.  } 
  8.  else if (handler instanceof HandlerMethod) { 
  9.   HandlerMethod handlerMethod = (HandlerMethod) handler; 
  10.   handler = handlerMethod.getBean(); 
  11.   // 交給父類判斷 
  12.   return super.shouldApplyTo(request, handler); 
  13.  } 
  14.  else { 
  15.   // 不支持 
  16.   return false; 
  17.  } 

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

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

用幾個(gè)成員變量說(shuō)一下處理過(guò)程,就不貼過(guò)多的代碼了

ExceptionHandlerExceptionResolver

 
 
 
 
  1. // 省略了繼承和實(shí)現(xiàn)關(guān)系 
  2. public class ExceptionHandlerExceptionResolver { 
  3.  
  4.  @Nullable 
  5.  private HandlerMethodArgumentResolverComposite argumentResolvers; 
  6.  
  7.  @Nullable 
  8.  private HandlerMethodReturnValueHandlerComposite returnValueHandlers; 
  9.  
  10.  private List> messageConverters; 
  11.  
  12.  
  13.  // 被@RequestMapping標(biāo)記的類 -> ExceptionHandlerMethodResolver 
  14.  private final Map, ExceptionHandlerMethodResolver>  
  15.  exceptionHandlerCache = new ConcurrentHashMap<>(64); 
  16.  
  17.  // 被@ControllerAdvice注解標(biāo)記的類 -> ExceptionHandlerMethodResolver 
  18.  private final Map 
  19.  exceptionHandlerAdviceCache = new LinkedHashMap<>(); 

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

例如從如下方法可以知道,支持的參數(shù)類型為@SessionAttribute,@RequestAttribute等 如果你寫(xiě)個(gè)@RequestParam是肯定不會(huì)注入進(jìn)來(lái)的

 
 
 
 
  1. protected List getDefaultArgumentResolvers() { 
  2.  List resolvers = new ArrayList<>(); 
  3.  
  4.  // Annotation-based argument resolution 
  5.  resolvers.add(new SessionAttributeMethodArgumentResolver()); 
  6.  resolvers.add(new RequestAttributeMethodArgumentResolver()); 
  7.  
  8.  // Type-based argument resolution 
  9.  resolvers.add(new ServletRequestMethodArgumentResolver()); 
  10.  resolvers.add(new ServletResponseMethodArgumentResolver()); 
  11.  resolvers.add(new RedirectAttributesMethodArgumentResolver()); 
  12.  resolvers.add(new ModelMethodProcessor()); 
  13.  
  14.  // Custom arguments 
  15.  if (getCustomArgumentResolvers() != null) { 
  16.   resolvers.addAll(getCustomArgumentResolvers()); 
  17.  } 
  18.  
  19.  return resolvers; 

最重要的4個(gè)map來(lái)了,ExceptionHandlerExceptionResolver的工作過(guò)程主要就是操作這4個(gè)map

 
 
 
 
  1. // 省略了繼承和實(shí)現(xiàn)關(guān)系 
  2. public class ExceptionHandlerExceptionResolver { 
  3.  
  4.  // 被@RequestMapping標(biāo)記的類 -> ExceptionHandlerMethodResolver 
  5.  private final Map, ExceptionHandlerMethodResolver> 
  6.  exceptionHandlerCache = new ConcurrentHashMap<>(64); 
  7.  
  8.  // 被@ControllerAdvice注解標(biāo)記的類 -> ExceptionHandlerMethodResolver 
  9.  private final Map 
  10.  exceptionHandlerAdviceCache = new LinkedHashMap<>(); 
  11.     

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

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

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

以最開(kāi)始的例子演示,ExceptionHandlerExceptionResolver初始化后

此時(shí)exceptionHandlerCache是沒(méi)有值的 訪問(wèn)如下鏈接后

 
 
 
 
  1. http://localhost:8080/location/getLocationInfo 

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

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

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

接著來(lái)看剩下的2個(gè)Map

 
 
 
 
  1. public class ExceptionHandlerMethodResolver { 
  2.  
  3.  
  4.  // 異常 -> 對(duì)應(yīng)的處理方法 
  5.  private final Map, Method> 
  6.  mappedMethods = new HashMap<>(16); 
  7.  
  8.  // 異常 -> 對(duì)應(yīng)的處理方法 
  9.  // 這個(gè)是基于mappedMethods又做了一次緩存 
  10.  // 為什么要再做一次緩存呢? 
  11.  // 是因?yàn)楦鶕?jù)異常類型獲取處理方法的時(shí)候,一個(gè)異??赡苡卸鄠€(gè)處理方法,即一個(gè)異常會(huì)從mappedMethods中查出多個(gè)處理方法 
  12.  // 最后返回的是繼承關(guān)系最近的異常對(duì)應(yīng)的處理方法,所以在查找的時(shí)候又做了一次緩存,避免每次查mappedMethods然后取最優(yōu)值 
  13.  // 從exceptionLookupCache中就可以直接查到最優(yōu)的處理方法 
  14.  private final Map, Method> 
  15.  exceptionLookupCache = new ConcurrentReferenceHashMap<>(16); 
  16.   

@ControllerAdvice的mappedMethods是在ExceptionHandlerExceptionResolver初始化的過(guò)程中賦值的

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

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

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

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

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

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

ResponseStatusExceptionResolver

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

ResponseStatusExceptionResolver主要用來(lái)處理如下異常

拋出的異常類型繼承自ResponseStatusException

拋出的異常類型被@ResponseStatus標(biāo)記

以一個(gè)例子來(lái)演示這個(gè)處理器的功能

 
 
 
 
  1. @ResponseStatus(HttpStatus.UNAUTHORIZED) 
  2. public class UnauthorizedException extends RuntimeException { 
  3. @RestController 
  4. @RequestMapping("shoppingCar") 
  5. public class ShoppingCarController { 
  6.  
  7.  @RequestMapping("getCarInfo") 
  8.  public String index() { 
  9.   throw new UnauthorizedException(); 
  10.  } 

訪問(wèn)

 
 
 
 
  1. http://localhost:8080/shoppingCar/getCarInfo 

顯示如下

DefaultHandlerExceptionResolver

用來(lái)處理一些常見(jiàn)的Http異常,如

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

執(zhí)行入口

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

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

 
 
 
 
  1. @Nullable 
  2. private List handlerExceptionResolvers; 

本文轉(zhuǎn)載自微信公眾號(hào)「Java識(shí)堂」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系Java識(shí)堂公眾號(hào)。


當(dāng)前標(biāo)題:SpringMVC異常解析器,原理就是這么簡(jiǎn)單
本文來(lái)源:http://m.5511xx.com/article/djodcsc.html