自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

SpringMVC 異常處理體系深入分析

開發(fā) 架構
SpringMVC 中針對異常問題有一套完整的處理體系,這套體系非常好用,今天松哥就花點時間來和大家聊一聊 SpringMVC 中的異常處理體系,我們把 SpringMVC 中的異常體系從頭到尾梳理一下。

[[392859]]

SpringMVC 中針對異常問題有一套完整的處理體系,這套體系非常好用,今天松哥就花點時間來和大家聊一聊 SpringMVC 中的異常處理體系,我們把 SpringMVC 中的異常體系從頭到尾梳理一下。

1.異常解析器概覽

在 SpringMVC 的異常體系中,處于最頂層的大 Boss 是 HandlerExceptionResolver,這是一個接口,里邊只有一個方法:

  1. public interface HandlerExceptionResolver { 
  2.  @Nullable 
  3.  ModelAndView resolveException( 
  4.    HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex); 

resolveException 方法就用來解析請求處理過程中所產生的異常,并最終返回一個 ModelAndView。

我們來看下 HandlerExceptionResolver 的實現類:

直接實現 HandlerExceptionResolver 接口的類有三個:

  • HandlerExceptionResolverComposite:這個一看又是一個組合,在最近的源碼分析中我們已經多次見到 xxxComposite 了,這里就不再贅述。
  • DefaultErrorAttributes:這個用來保存異常屬性。
  • AbstractHandlerExceptionResolver:這個的子類比較多:
    • SimpleMappingExceptionResolver:通過提前配置好的異常類和 View 之間的對應關系來解析異常。
    • AbstractHandlerMethodExceptionResolver:處理使用 @ExceptionHandler 注解自定義的異常類型。
    • DefaultHandlerExceptionResolver:按照不同類型來處理異常。
    • ResponseStatusExceptionResolver:處理含有 @ResponseStatus 注解的異常。

在 SpringMVC 中,大致的異常解析器就是這些,接下來我們來逐個學習這些異常解析器。

2.AbstractHandlerExceptionResolver

AbstractHandlerExceptionResolver 是真正干活的異常解析器的父類,我們就先從他的 resolveException 方法開始看起。

  1. @Override 
  2. @Nullable 
  3. public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { 
  4.  if (shouldApplyTo(request, handler)) { 
  5.   prepareResponse(ex, response); 
  6.   ModelAndView result = doResolveException(request, response, handler, ex); 
  7.   if (result != null) { 
  8.    logException(ex, request); 
  9.   } 
  10.   return result; 
  11.  } 
  12.  else { 
  13.   return null
  14.  } 
  1. 首先調用 shouldApplyTo 方法判斷當前解析器是否可以處理傳入的處理器所拋出的異常,如果不支持,則直接返回 null,這個異常將交給下一個 HandlerExceptionResolver 去處理。
  2. 調用 prepareResponse 方法處理 response。
  3. 調用 doResolveException 方法實際處理異常,這是一個模版方法,具體的實現在子類中。
  4. 調用 logException 方法記錄異常日志信息。

記錄異常日志沒啥好說的,doResolveException 則是一個空的模版方法,所以這里對我們來說主要就是兩個方法:shouldApplyTo 和 prepareResponse,我們分別來看。

shouldApplyTo

  1. protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) { 
  2.  if (handler != null) { 
  3.   if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) { 
  4.    return true
  5.   } 
  6.   if (this.mappedHandlerClasses != null) { 
  7.    for (Class<?> handlerClass : this.mappedHandlerClasses) { 
  8.     if (handlerClass.isInstance(handler)) { 
  9.      return true
  10.     } 
  11.    } 
  12.   } 
  13.  } 
  14.  return !hasHandlerMappings(); 

這里涉及到了兩個對象:mappedHandlers 和 mappedHandlerClasses:

  • mappedHandlers:存儲的是處理器對象(Controller 或者 Controller 中的方法)
  • mappedHandlerClasses:存儲的是處理器的 Class。

我們在配置異常解析器的時候可以配置這兩個對象,進而實現該異常處理器只為某一個處理器服務,但是一般來說沒這種需求,所以大家僅做了解即可。

如果開發(fā)者一開始配置了 mappedHandlers 或者 mappedHandlerClasses,則用這兩個和處理器去比較,否則就直接返回 true,表示支持該異常處理。

prepareResponse

prepareResponse 方法比較簡單,主要是處理一下響應頭的緩存字段。

  1. protected void prepareResponse(Exception ex, HttpServletResponse response) { 
  2.  if (this.preventResponseCaching) { 
  3.   preventCaching(response); 
  4.  } 
  5. protected void preventCaching(HttpServletResponse response) { 
  6.  response.addHeader(HEADER_CACHE_CONTROL, "no-store"); 

這是 AbstractHandlerExceptionResolver 的大致內容,可以看到還是非常 easy 的,接下來我們來看它的實現類。

2.1 AbstractHandlerMethodExceptionResolver

AbstractHandlerMethodExceptionResolver 主要是重寫了 shouldApplyTo 方法和 doResolveException 方法,一個一個來看。

shouldApplyTo

  1. @Override 
  2. protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) { 
  3.  if (handler == null) { 
  4.   return super.shouldApplyTo(request, null); 
  5.  } 
  6.  else if (handler instanceof HandlerMethod) { 
  7.   HandlerMethod handlerMethod = (HandlerMethod) handler; 
  8.   handler = handlerMethod.getBean(); 
  9.   return super.shouldApplyTo(request, handler); 
  10.  } 
  11.  else if (hasGlobalExceptionHandlers() && hasHandlerMappings()) { 
  12.   return super.shouldApplyTo(request, handler); 
  13.  } 
  14.  else { 
  15.   return false
  16.  } 

這塊感覺沒啥好說的,判斷邏輯基本上都還是調用父類的 shouldApplyTo 方法去處理。

doResolveException

  1. @Override 
  2. @Nullable 
  3. protected final ModelAndView doResolveException( 
  4.   HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { 
  5.  HandlerMethod handlerMethod = (handler instanceof HandlerMethod ? (HandlerMethod) handler : null); 
  6.  return doResolveHandlerMethodException(request, response, handlerMethod, ex); 
  7. @Nullable 
  8. protected abstract ModelAndView doResolveHandlerMethodException( 
  9.   HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception ex); 

doResolveException 是具體的異常處理方法,但是它里邊卻沒有實質性操作,具體的事情交給 doResolveHandlerMethodException 方法去做了,而該方法是一個抽象方法,具體的實現在子類中。

2.1.1 ExceptionHandlerExceptionResolver

AbstractHandlerMethodExceptionResolver 只有一個子類就是 ExceptionHandlerExceptionResolver,來看下它的 doResolveHandlerMethodException 方法:

  1. @Override 
  2. @Nullable 
  3. protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, 
  4.   HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) { 
  5.  ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception); 
  6.  if (exceptionHandlerMethod == null) { 
  7.   return null
  8.  } 
  9.  if (this.argumentResolvers != null) { 
  10.   exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); 
  11.  } 
  12.  if (this.returnValueHandlers != null) { 
  13.   exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); 
  14.  } 
  15.  ServletWebRequest webRequest = new ServletWebRequest(request, response); 
  16.  ModelAndViewContainer mavContainer = new ModelAndViewContainer(); 
  17.  ArrayList<Throwable> exceptions = new ArrayList<>(); 
  18.  try { 
  19.   if (logger.isDebugEnabled()) { 
  20.    logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod); 
  21.   } 
  22.   // Expose causes as provided arguments as well 
  23.   Throwable exToExpose = exception; 
  24.   while (exToExpose != null) { 
  25.    exceptions.add(exToExpose); 
  26.    Throwable cause = exToExpose.getCause(); 
  27.    exToExpose = (cause != exToExpose ? cause : null); 
  28.   } 
  29.   Object[] arguments = new Object[exceptions.size() + 1]; 
  30.   exceptions.toArray(arguments);  // efficient arraycopy call in ArrayList 
  31.   arguments[arguments.length - 1] = handlerMethod; 
  32.   exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments); 
  33.  } 
  34.  catch (Throwable invocationEx) { 
  35.   // Any other than the original exception (or a cause) is unintended here, 
  36.   // probably an accident (e.g. failed assertion or the like). 
  37.   if (!exceptions.contains(invocationEx) && logger.isWarnEnabled()) { 
  38.    logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx); 
  39.   } 
  40.   // Continue with default processing of the original exception... 
  41.   return null
  42.  } 
  43.  if (mavContainer.isRequestHandled()) { 
  44.   return new ModelAndView(); 
  45.  } 
  46.  else { 
  47.   ModelMap model = mavContainer.getModel(); 
  48.   HttpStatus status = mavContainer.getStatus(); 
  49.   ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status); 
  50.   mav.setViewName(mavContainer.getViewName()); 
  51.   if (!mavContainer.isViewReference()) { 
  52.    mav.setView((View) mavContainer.getView()); 
  53.   } 
  54.   if (model instanceof RedirectAttributes) { 
  55.    Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); 
  56.    RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); 
  57.   } 
  58.   return mav; 
  59.  } 

這個方法雖然比較長,但是很好理解:

  1. 首先查找到帶有 @ExceptionHandler 注解的方法,封裝成一個 ServletInvocableHandlerMethod 對象(關于 ServletInvocableHandlerMethod 對象,松哥在之前的文章中已經介紹過了,具體參見:Spring Boot 定義接口的方法是否可以聲明為 private?)。
  2. 如果找到了對應的方法,則為 exceptionHandlerMethod 配置參數解析器、視圖解析器等,關于這些解析器,參考松哥之前的文章:SpringBoot 中如何自定義參數解析器?、深入分析 SpringMVC 參數解析器、Spring Boot 中如何統(tǒng)一 API 接口響應格式?。
  3. 接下來定義一個 exceptions 數組,如果發(fā)生的異常存在異常鏈,則將整個異常鏈存入 exceptions 數組中。
  4. exceptions 數組再加上 handlerMethod,共同組成方法參數,調用 exceptionHandlerMethod.invokeAndHandle 完成自定義異常方法的執(zhí)行,執(zhí)行結果被保存再 mavContainer 中。
  5. 如果請求到此結束,則直接構造一個 ModelAndView 返回。
  6. 否則從 mavContainer 中取出各項信息,構建新的 ModelAndView 返回。同時,如果存在重定向參數,也將之保存下來(關于重定向參數,參見:SpringMVC 中的參數還能這么傳遞?漲姿勢了!)。

這就是 ExceptionHandlerExceptionResolver 的大致工作流程,可以看到,還是非常 easy 的。

2.2 DefaultHandlerExceptionResolver

這個看名字就知道是一個默認的異常處理器,用來處理一些常見的異常類型,我們來看一下它的 doResolveException 方法:

  1. @Override 
  2. @Nullable 
  3. protected ModelAndView doResolveException( 
  4.   HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { 
  5.  try { 
  6.   if (ex instanceof HttpRequestMethodNotSupportedException) { 
  7.    return handleHttpRequestMethodNotSupported( 
  8.      (HttpRequestMethodNotSupportedException) ex, request, response, handler); 
  9.   } 
  10.   else if (ex instanceof HttpMediaTypeNotSupportedException) { 
  11.    return handleHttpMediaTypeNotSupported( 
  12.      (HttpMediaTypeNotSupportedException) ex, request, response, handler); 
  13.   } 
  14.   else if (ex instanceof HttpMediaTypeNotAcceptableException) { 
  15.    return handleHttpMediaTypeNotAcceptable( 
  16.      (HttpMediaTypeNotAcceptableException) ex, request, response, handler); 
  17.   } 
  18.   else if (ex instanceof MissingPathVariableException) { 
  19.    return handleMissingPathVariable( 
  20.      (MissingPathVariableException) ex, request, response, handler); 
  21.   } 
  22.   else if (ex instanceof MissingServletRequestParameterException) { 
  23.    return handleMissingServletRequestParameter( 
  24.      (MissingServletRequestParameterException) ex, request, response, handler); 
  25.   } 
  26.   else if (ex instanceof ServletRequestBindingException) { 
  27.    return handleServletRequestBindingException( 
  28.      (ServletRequestBindingException) ex, request, response, handler); 
  29.   } 
  30.   else if (ex instanceof ConversionNotSupportedException) { 
  31.    return handleConversionNotSupported( 
  32.      (ConversionNotSupportedException) ex, request, response, handler); 
  33.   } 
  34.   else if (ex instanceof TypeMismatchException) { 
  35.    return handleTypeMismatch( 
  36.      (TypeMismatchException) ex, request, response, handler); 
  37.   } 
  38.   else if (ex instanceof HttpMessageNotReadableException) { 
  39.    return handleHttpMessageNotReadable( 
  40.      (HttpMessageNotReadableException) ex, request, response, handler); 
  41.   } 
  42.   else if (ex instanceof HttpMessageNotWritableException) { 
  43.    return handleHttpMessageNotWritable( 
  44.      (HttpMessageNotWritableException) ex, request, response, handler); 
  45.   } 
  46.   else if (ex instanceof MethodArgumentNotValidException) { 
  47.    return handleMethodArgumentNotValidException( 
  48.      (MethodArgumentNotValidException) ex, request, response, handler); 
  49.   } 
  50.   else if (ex instanceof MissingServletRequestPartException) { 
  51.    return handleMissingServletRequestPartException( 
  52.      (MissingServletRequestPartException) ex, request, response, handler); 
  53.   } 
  54.   else if (ex instanceof BindException) { 
  55.    return handleBindException((BindException) ex, request, response, handler); 
  56.   } 
  57.   else if (ex instanceof NoHandlerFoundException) { 
  58.    return handleNoHandlerFoundException( 
  59.      (NoHandlerFoundException) ex, request, response, handler); 
  60.   } 
  61.   else if (ex instanceof AsyncRequestTimeoutException) { 
  62.    return handleAsyncRequestTimeoutException( 
  63.      (AsyncRequestTimeoutException) ex, request, response, handler); 
  64.   } 
  65.  } 
  66.  catch (Exception handlerEx) { 
  67.  } 
  68.  return null

可以看到,這里實際上就是根據不同的異常類型,然后調用不同的類去處理該異常。這里相關的處理都比較容易,以 HttpRequestMethodNotSupportedException 為例,異常處理就是對 response 對象做一些配置,如下:

  1. protected ModelAndView handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, 
  2.   HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException { 
  3.  String[] supportedMethods = ex.getSupportedMethods(); 
  4.  if (supportedMethods != null) { 
  5.   response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", ")); 
  6.  } 
  7.  response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage()); 
  8.  return new ModelAndView(); 

配置響應頭,然后 sendError,最后返回一個空的 ModelAndView 對象。

其實這里哥哥異常處理方法都大同小異,松哥就不再贅述啦。

2.3 ResponseStatusExceptionResolver

這個用來處理 ResponseStatusException 類型的異常,或者使用了 @ResponseStatus 注解標記的普通異常類。我們來看下它的 doResolveException 方法:

  1. @Override 
  2. @Nullable 
  3. protected ModelAndView doResolveException( 
  4.   HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { 
  5.  try { 
  6.   if (ex instanceof ResponseStatusException) { 
  7.    return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler); 
  8.   } 
  9.   ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class); 
  10.   if (status != null) { 
  11.    return resolveResponseStatus(status, request, response, handler, ex); 
  12.   } 
  13.   if (ex.getCause() instanceof Exception) { 
  14.    return doResolveException(request, response, handler, (Exception) ex.getCause()); 
  15.   } 
  16.  } 
  17.  catch (Exception resolveEx) { 
  18.  } 
  19.  return null

可以看到,首先判斷異常類型是不是 ResponseStatusException,如果是,則直接調用 resolveResponseStatusException 方法進行異常信息處理,如果不是,則去查找到異常類上的 @ResponseStatus 注解,并從中查找出相關的異常信息,然后調用 resolveResponseStatus 方法進行處理。

可以看到,ResponseStatusExceptionResolver 處理的異常類型有兩種:

  • 直接繼承自 ResponseStatusException 的異常類,這種異常類可以直接從里邊提取出來想要的信息。
  • 通過 @ResponseStatus 注解的普通異常類,這種情況下異常信息從 @ResponseStatus 注解中提取出來。

這個比較簡單,沒啥好說的。

2.4 SimpleMappingExceptionResolver

SimpleMappingExceptionResolver 則是根據不同的異常顯示不同的 error 頁面??赡苡械男』锇檫€沒用過 SimpleMappingExceptionResolver,所以松哥這里先簡單說一下用法。

SimpleMappingExceptionResolver 的配置非常簡單,直接提供一個 SimpleMappingExceptionResolver 的實例即可,如下:

  1. @Bean 
  2. SimpleMappingExceptionResolver simpleMappingExceptionResolver() { 
  3.     SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver(); 
  4.     Properties mappings = new Properties(); 
  5.     mappings.put("java.lang.ArithmeticException""11"); 
  6.     mappings.put("java.lang.NullPointerException""22"); 
  7.     resolver.setExceptionMappings(mappings); 
  8.     Properties statusCodes = new Properties(); 
  9.     statusCodes.put("11""500"); 
  10.     statusCodes.put("22""500"); 
  11.     resolver.setStatusCodes(statusCodes); 
  12.     return resolver; 

在 mappings 中配置異常和 view 之間的對應關系,要寫異常類的全路徑,后面的 11、22 則表示視圖名稱;statusCodes 中配置了視圖和響應狀態(tài)碼之間的映射關系。配置完成后,如果我們的項目在運行時拋出了 ArithmeticException 異常,則會展示出 11 視圖,如果我們的項目在運行時拋出了 NullPointerException 異常,則會展示出 22 視圖。

這是用法,了解了用法之后我們再來看源碼,就容易理解了,我們直接來看 doResolveException 方法:

  1. @Override 
  2. @Nullable 
  3. protected ModelAndView doResolveException( 
  4.   HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { 
  5.  String viewName = determineViewName(ex, request); 
  6.  if (viewName != null) { 
  7.   Integer statusCode = determineStatusCode(request, viewName); 
  8.   if (statusCode != null) { 
  9.    applyStatusCodeIfPossible(request, response, statusCode); 
  10.   } 
  11.   return getModelAndView(viewName, ex, request); 
  12.  } 
  13.  else { 
  14.   return null
  15.  } 
  1. 首先調用 determineViewName 方法確定視圖的名稱。
  2. 接下來調用 determineStatusCode 查看視圖是否有對應的 statusCode。
  3. 調用 applyStatusCodeIfPossible 方法將 statusCode 設置到 response 上,這個方法很簡單,不多說。
  4. 調用 getModelAndView 方法構造一個 ModelAndView 對象返回,在構造時,同時設置異常參數,異常的信息的 key 默認就是 exception。

在上面這個過程中,有兩個比較長的方法,松哥這里需要和大家額外多說兩句。

determineViewName

這個就是根據異常類型找到視圖名,我們來看下具體的查找方式:

  1. @Nullable 
  2. protected String determineViewName(Exception ex, HttpServletRequest request) { 
  3.  String viewName = null
  4.  if (this.excludedExceptions != null) { 
  5.   for (Class<?> excludedEx : this.excludedExceptions) { 
  6.    if (excludedEx.equals(ex.getClass())) { 
  7.     return null
  8.    } 
  9.   } 
  10.  } 
  11.  if (this.exceptionMappings != null) { 
  12.   viewName = findMatchingViewName(this.exceptionMappings, ex); 
  13.  } 
  14.  if (viewName == null && this.defaultErrorView != null) { 
  15.   viewName = this.defaultErrorView; 
  16.  } 
  17.  return viewName; 
  1. 如果當前異常包含在 excludedExceptions 中,則直接返回 null(意思是當前異常被忽略處理了,直接按照默認方式來)。
  2. 如果 exceptionMappings 不為 null,則直接調用 findMatchingViewName 方法查找異常對應的視圖名(exceptionMappings 變量就是前面我們配置的映射關系),具體的查找方式就是遍歷我們前面配置的映射表。
  3. 如果沒找到對應的 viewName,并且用戶配置了 defaultErrorView,則將 defaultErrorView 賦值給 viewName,并將 viewName 返回。

determineStatusCode

  1. @Nullable 
  2. protected Integer determineStatusCode(HttpServletRequest request, String viewName) { 
  3.  if (this.statusCodes.containsKey(viewName)) { 
  4.   return this.statusCodes.get(viewName); 
  5.  } 
  6.  return this.defaultStatusCode; 

這個就比較容易,直接去 statusCodes 中查看是否有視圖對應的狀態(tài)碼,如果有則直接返回,如果沒有,就返回一個默認的。

3.HandlerExceptionResolverComposite

最后,還有一個 HandlerExceptionResolverComposite 需要和大家介紹下,這是一個組合的異常處理器,用來代理哪些真正干活的異常處理器。

  1. @Override 
  2. @Nullable 
  3. public ModelAndView resolveException( 
  4.   HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { 
  5.  if (this.resolvers != null) { 
  6.   for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) { 
  7.    ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex); 
  8.    if (mav != null) { 
  9.     return mav; 
  10.    } 
  11.   } 
  12.  } 
  13.  return null

它的 resolveException 方法就比較簡單了,這種寫法我們已經見到過很多次了,不再贅述。

4.小結

好啦,今天就和大家簡單聊一聊 SpringMVC 中的異常處理體系,整體來說并不難,小伙伴們可以仔細品一品。

本文轉載自微信公眾號「江南一點雨」,可以通過以下二維碼關注。轉載本文請聯系江南一點雨公眾號。

 

責任編輯:武曉燕 來源: 江南一點雨
相關推薦

2021-03-18 10:56:59

SpringMVC參數解析器

2021-03-26 11:00:50

SpringMVC組件接口

2010-10-14 12:44:02

無線LAN故障處理

2010-09-07 14:21:22

PPPoE協(xié)議

2022-04-12 08:30:45

TomcatWeb 應用Servlet

2011-03-23 11:01:55

LAMP 架構

2010-03-08 14:53:48

Linux分區(qū)

2011-09-01 13:51:52

JavaScript

2023-02-01 08:13:30

Redis內存碎片

2022-08-30 07:00:18

執(zhí)行引擎Hotspot虛擬機

2009-06-10 18:12:38

Equinox動態(tài)化OSGi動態(tài)化

2021-10-29 16:36:53

AMSAndroidActivityMan

2009-12-14 14:50:46

Ruby傳參數

2009-12-16 16:39:01

Visual Stud

2018-12-18 10:11:37

軟件復雜度軟件系統(tǒng)軟件開發(fā)

2015-08-03 09:54:26

Java線程Java

2018-10-25 15:24:10

ThreadLocal內存泄漏Java

2020-12-07 06:23:48

Java內存

2013-11-14 17:02:41

Android多窗口

2011-09-13 09:08:22

架構
點贊
收藏

51CTO技術棧公眾號