框架進行時——SpringMVC流程簡析(一)

来源:https://www.cnblogs.com/ideal2015/archive/2022/11/18/16899412.html
-Advertisement-
Play Games

基於 SpringWeb(5.3.23)的介面請求分析 前情提要 假定當前 Web 項目中有如下實體類和介面: package com.example.entity; public class WebUser { private String name; private Integer age; p ...


基於 SpringWeb(5.3.23)的介面請求分析

前情提要

假定當前 Web 項目中有如下實體類和介面:

package com.example.entity;

public class WebUser {
  private String name;
  private Integer age;
  private LocalDate birthday;
  private Boolean gender;
  
  // getter、setter、toString ...
}
package com.example.controller;

@RestController @RequestMapping("/test")
public class WebController {

  @RequestMapping("/1")
  public String test1(HttpServletRequest request, @RequestParam Map<String, Object> params, WebUser webUser) {
    System.out.println("request.getClass() = " + request.getClass());
    System.out.println("params = " + params);
    System.out.println("webUser = " + webUser);
    return UUID.randomUUID().toString();
  }
}

使用 Postman 發送請求測試,結果符合預期:

postman截圖
服務端日誌

下麵就一些關鍵點進行探究分析。

由誰來處理本次請求?——請求處理器

SpringMVC 中定義了多種“處理器映射”HandlerMapping,用來根據特定的請求返回對應的處理器(handler)。處理器映射的介面聲明如下:

package org.springframework.web.servlet;

public interface HandlerMapping {
  
  // 根據具體的請求返回一個處理器執行器鏈
  HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

處理器執行器鏈HandlerExecutionChain由處理器和圍繞該處理器的所有處理器攔截器HandlerInterceptor組成,是對處理器的一層封裝(下文中“處理器”一詞也代指“處理器執行器鏈”,如無特殊說明不再區分)。

SpringMVC 中預設的處理器映射有以下5個:

0 = {RequestMappingHandlerMapping@7031} (order=0)
1 = {BeanNameUrlHandlerMapping@7032} (order=2)
2 = {RouterFunctionMapping@7033} (order=3)
3 = {SimpleUrlHandlerMapping@7034} (order=2147483646)
4 = {WelcomePageHandlerMapping@7035} (order=2147483647)

當 DispatcherServlet 被初始化時,如果處理器映射有多個,DispatcherServlet 會調用

// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);

對它們進行排序。其中的排序規則如下:

  1. 實現了PriorityOrdered介面的優先。
  2. 實現了Ordered介面的優先,然後按照 getOrder() 的值從小到大排序。
  3. 如以上條件均不滿足,則排到最後。
// 摘自 org.springframework.core.OrderComparator
private int compare(Object o1, Object o2) {
  boolean p1 = o1 instanceof PriorityOrdered;
  boolean p2 = o2 instanceof PriorityOrdered;
  if (p1 && !p2) {
    return -1;
  } else if (p2 && !p1) {
    return 1;
  } else {
    int i1 = this.getOrder(o1);
    int i2 = this.getOrder(o2);
    return Integer.compare(i1, i2);
  }
}

private int getOrder(Object obj) {
  if (obj != null && obj instanceof Ordered) {
    return ((Ordered)obj).getOrder();
  }
  return Ordered.LOWEST_PRECEDENCE;
}

由此可見,一個處理器映射可以通過實現Ordered介面後重寫 getOrder() 方法以指定自身的次序。
此外,AbstractHandlerMapping類中定義了一個order屬性,繼承了該抽象類的子類也可調用 setOrder 方法間接地指定自身的次序。

處理器需要由處理器適配器HandlerAdapter來執行。處理器適配器的介面聲明如下:

package org.springframework.web.servlet;

public interface HandlerAdapter {
  // 判斷是否支持給定的處理器
  boolean supports(Object handler);

  // 使用給定的處理器對本次請求進行處理
  ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
}

SpringMVC 中預設的處理器適配器有如下4個(同處理器映射一樣,它們也會被排序):

0 = {RequestMappingHandlerAdapter@6792}
1 = {HandlerFunctionAdapter@6793}
2 = {HttpRequestHandlerAdapter@6794}
3 = {SimpleControllerHandlerAdapter@6795}

常用的適配器就是第一個RequestMappingHandlerAdapter,它的支持規則很簡單——處理器是HandlerMethod類型的即可。而RequestMappingHandlerMapping返回的處理器正是這一類型。

註意到 org.springframework.web.servlet.DispatcherServlet#doDispatch 方法中有如下代碼片段:

// Determine handler for the current request.
HandlerExecutionChain mappedHandler = getHandler(processedRequest);

// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// Actually invoke the handler.
ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

即分三步:獲取處理器、獲取適配器、使用適配器執行處理器。

獲取處理器,就是按照次序遍歷所有的處理器映射,找到第一個非空的處理器執:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  if (this.handlerMappings == null) {
    return null;
  }
  for (HandlerMapping mapping : this.handlerMappings) {
    HandlerExecutionChain handler = mapping.getHandler(request);
    if (handler != null) {
      return handler;
    }
  }
  return null;
}

獲取適配器的,就是遍歷所有的適配器,找到第一個支持當前處理器的適配器:

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
  if (this.handlerAdapters != null) {
    for (HandlerAdapter adapter : this.handlerAdapters) {
      if (adapter.supports(handler)) {
        return adapter;
      }
    }
  }
  throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

誰來為介面的請求參數賦值?——參數解析器

在案例中,介面可直接使用定義的三個參數 request、params 和 webUser,但並沒有顯示地為它們賦值——這是由 SpringMVC 的參數解析器自動完成的。

SpringMVC 中定義了多種參數解析器HandlerMethodArgumentResolver,特定的解析器支持在特定的條件下為參數賦值。參數解析器的介面聲明如下:

package org.springframework.web.method.support;

public interface HandlerMethodArgumentResolver {
  // 是否支持這樣的參數,即當參數滿足什麼樣的條件時可以為其賦值
  boolean supportsParameter(MethodParameter parameter);
  
  // 如何為參數賦值
  Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
}

參數解析器有一個重要的組合器實現HandlerMethodArgumentResolverComposite,用於管理其他的參數解析器,其核心代碼如下:

package org.springframework.web.method.support;

public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver { 
  // 管理所有的參數解析器
  private final List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();
  // 緩存後不必每次都遍歷所有
  private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache = new ConcurrentHashMap<>(256);
  
  // 檢查所管理的參數解析器,看其中是否有支持的
  @Override
  public boolean supportsParameter(MethodParameter parameter) {
    return getArgumentResolver(parameter) != null;
  }

  // 用第一個支持此參數的解析器來處理
  @Override @Nullable
  public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
    if (resolver == null) {
      throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
    }
    return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
  }

  // 遍歷所有已註冊的參數解析器,找到第一個支持這種參數的。如果未找到則返回 null
  @Nullable
  private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
    if (result != null) {
      return result;
    }
    for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
      if (resolver.supportsParameter(parameter)) {
        this.argumentResolverCache.put(parameter, resolver);
        return resolver;
      }
    }
    // 通常來說不可能走到這裡,因為最後一個解析器 ServletModelAttributeMethodProcessor 是幾乎“萬能”的
  }
}

預設的參數解析器(此處的 this.argumentResolvers)有27個,它們各自對 supportsParameter 方法的實現如下(按順序排列):

0 = {RequestParamMethodArgumentResolver@6892}

/*
 * 滿足以下三類條件中的任意一類:
 *   1. 參數標有 @RequestParam 註解、且類型不是 Map 或其子類
 *   2. 參數標有 @RequestParam 註解、且類型是 Map 或其子類、且 @RequestParam 註解的"name"或"value"屬性不為空
 *   3. 參數類型是 MultipartFile 或 Part(包括它們的數組或集合)、且沒有標 @RequestPart 註解
 */
public boolean supportsParameter0(MethodParameter parameter) {
  if (parameter.hasParameterAnnotation(RequestParam.class)) {
    if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
      RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
      return (requestParam != null && StringUtils.hasText(requestParam.name()));
    } else {
      return true;
    }
  } else {
    if (parameter.hasParameterAnnotation(RequestPart.class)) {
      return false;
    }
    parameter = parameter.nestedIfOptional();
    return MultipartResolutionDelegate.isMultipartArgument(parameter);
  }
}

1 = {RequestParamMapMethodArgumentResolver@6893}

/*
 * 同時滿足以下三個條件:
 *   1. 參數類型是 Map 或其子類
 *   2. 參數標有 @RequestParam 註解
 *   3. @RequestParam 註解未設置"name"或"value"屬性
 */
public boolean supportsParameter(MethodParameter parameter) {
  RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
  return requestParam != null && Map.class.isAssignableFrom(parameter.getParameterType()) && !StringUtils.hasText(requestParam.name());
}

2 = {PathVariableMethodArgumentResolver@6894}

/*
 * 滿足以下兩類條件中的任意一類:
 *   1. 參數標有 @PathVariable 註解、且類型不是 Map 或其子類
 *   2. 參數標有 @PathVariable 註解、且類型是 Map 或其子類、且 @PathVariable 註解的"name"或"value"屬性不為空
 */
public boolean supportsParameter(MethodParameter parameter) {
  if (!parameter.hasParameterAnnotation(PathVariable.class)) {
    return false;
  }
  if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
    PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
    return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
  }
  return true;
}

3 = {PathVariableMapMethodArgumentResolver@6895}

/*
 * 同時滿足以下三個條件:
 *   1. 參數類型是 Map 或其子類
 *   2. 參數標有 @PathVariable 註解
 *   3. @PathVariable 註解未設置"name"或"value"屬性
 */
public boolean supportsParameter(MethodParameter parameter) {
  PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
  return (pathVariable != null && Map.class.isAssignableFrom(parameter.getParameterType()) && !StringUtils.hasText(pathVariable.value()));
}

4 = {MatrixVariableMethodArgumentResolver@6896}

/*
 * 滿足以下兩類條件中的任意一類:
 *   1. 參數標有 @MatrixVariable 註解、且類型不是 Map 或其子類
 *   2. 參數標有 @MatrixVariable 註解、且類型是 Map 或其子類、且 @MatrixVariable 註解的"name"或"value"屬性不為空
 */
public boolean supportsParameter(MethodParameter parameter) {
  if (!parameter.hasParameterAnnotation(MatrixVariable.class)) {
    return false;
  }
  if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
    MatrixVariable matrixVariable = parameter.getParameterAnnotation(MatrixVariable.class);
    return (matrixVariable != null && StringUtils.hasText(matrixVariable.name()));
  }
  return true;
}

5 = {MatrixVariableMapMethodArgumentResolver@6897}

/*
 * 同時滿足以下三個條件:
 *   1. 參數類型是 Map 或其子類
 *   2. 參數標有 @MatrixVariable 註解
 *   3. @MatrixVariable 註解未設置"name"或"value"屬性
 */
public boolean supportsParameter(MethodParameter parameter) {
  MatrixVariable matrixVariable = parameter.getParameterAnnotation(MatrixVariable.class);
  return (matrixVariable != null && Map.class.isAssignableFrom(parameter.getParameterType()) && !StringUtils.hasText(matrixVariable.name()));
}

6 = {ServletModelAttributeMethodProcessor@6898}
ModelAttributeMethodProcessor

// 參數標有 @ModelAttribute 註解
public boolean supportsParameter(MethodParameter parameter) {
  return parameter.hasParameterAnnotation(ModelAttribute.class);
}

7 = {RequestResponseBodyMethodProcessor@6899}

// 參數標有 @RequestBody 註解
public boolean supportsParameter(MethodParameter parameter) {
  return parameter.hasParameterAnnotation(RequestBody.class);
}

8 = {RequestPartMethodArgumentResolver@6900}

/*
 * 滿足以下兩類條件中的任意一類:
 *   1. 參數標有 @RequestPart 註解
 *   2. 參數類型是 MultipartFile 或 Part(包括它們的數組或集合)、且沒有標 @RequestParam 註解
 */
public boolean supportsParameter(MethodParameter parameter) {
  if (parameter.hasParameterAnnotation(RequestPart.class)) {
    return true;
  }
  if (parameter.hasParameterAnnotation(RequestParam.class)) {
    return false;
  }
  return MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional());
}

9 = {RequestHeaderMethodArgumentResolver@6901}

/*
 * 同時滿足以下兩個條件:
 *   1. 參數標有 @RequestHeader 註解
 *   2. 參數類型不是 Map 或其子類
 */
public boolean supportsParameter(MethodParameter parameter) {
  return (parameter.hasParameterAnnotation(RequestHeader.class) && !Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType()));
}

10 = {RequestHeaderMapMethodArgumentResolver@6902}

/*
 * 同時滿足以下兩個條件:
 *   1. 參數標有 @RequestHeader 註解
 *   2. 參數類型是 Map 或其子類
 */
public boolean supportsParameter(MethodParameter parameter) {
  return (parameter.hasParameterAnnotation(RequestHeader.class) && Map.class.isAssignableFrom(parameter.getParameterType()));
}

11 = {ServletCookieValueMethodArgumentResolver@6903}
AbstractCookieValueMethodArgumentResolver

// 參數標有 @CookieValue 註解
public boolean supportsParameter(MethodParameter parameter) {
  return parameter.hasParameterAnnotation(CookieValue.class);
}

12 = {ExpressionValueMethodArgumentResolver@6904}

// 參數標有 @Value 註解
public boolean supportsParameter(MethodParameter parameter) {
  return parameter.hasParameterAnnotation(Value.class);
}

13 = {SessionAttributeMethodArgumentResolver@6905}

// 參數標有 @SessionAttribute 註解
public boolean supportsParameter(MethodParameter parameter) {
  return parameter.hasParameterAnnotation(SessionAttribute.class);
}

14 = {RequestAttributeMethodArgumentResolver@6906}

// 參數標有 @RequestAttribute 註解
public boolean supportsParameter(MethodParameter parameter) {
  return parameter.hasParameterAnnotation(RequestAttribute.class);
}

15 = {ServletRequestMethodArgumentResolver@6907}

/*
 * 滿足以下三類條件中的任意一類:
 *   1. 參數類型 Principal 或其子類,且沒有標任何註解
 *   2. 參數類型是 WebRequest、ServletRequest、MultipartRequest、HttpSession、PushBuilder、InputStream 或 Reader 或它們的子類
 *   2. 參數類型是 HttpMethod、Locale、TimeZone 或 ZoneId
 */
public boolean supportsParameter(MethodParameter parameter) {
  Class<?> paramType = parameter.getParameterType();
  return (Principal.class.isAssignableFrom(paramType) && !parameter.hasParameterAnnotations())
      || (WebRequest||ServletRequest||MultipartRequest||HttpSession||PushBuilder||InputStream||Reader).class.isAssignableFrom(paramType)
      || (HttpMethod||Locale||TimeZone||ZoneId).class == paramType;
}

16 = {ServletResponseMethodArgumentResolver@6908}

// 參數類型是 ServletResponse、OutputStream 或 Writer 或它們的子類
public boolean supportsParameter(MethodParameter parameter) {
  return (ServletResponse || OutputStream || Writer).class.isAssignableFrom(parameter.getParameterType());
}

17 = {HttpEntityMethodProcessor@6909}

// 參數類型是 HttpEntity 或 RequestEntity
public boolean supportsParameter(MethodParameter parameter) {
  Class<?> parameterType = parameter.getParameterType();
  return (parameterType == HttpEntity.class)  || (parameterType == RequestEntity.class);
}

18 = {RedirectAttributesMethodArgumentResolver@6910}

// 參數類型是 RedirectAttributes 或其子類
public boolean supportsParameter(MethodParameter parameter) {
  return RedirectAttributes.class.isAssignableFrom(parameter.getParameterType());
}

19 = {ModelMethodProcessor@6911}

// 參數類型是 Model 或其子類
public boolean supportsParameter(MethodParameter parameter) {
  return Model.class.isAssignableFrom(parameter.getParameterType());
}

20 = {MapMethodProcessor@6912}

/*
 * 同時滿足以下兩個條件:
 *   1. 參數類型是 Map 或其子類
 *   2. 參數沒有標任何註解
 */
public boolean supportsParameter(MethodParameter parameter) {
  return (Map.class.isAssignableFrom(parameter.getParameterType()) && parameter.getParameterAnnotations().length == 0);
}

21 = {ErrorsMethodArgumentResolver@6913}

// 參數類型是 Errors 或其子類
public boolean supportsParameter(MethodParameter parameter) {
  return Errors.class.isAssignableFrom(parameter.getParameterType());
}

22 = {SessionStatusMethodArgumentResolver@6914}

// 參數類型是 SessionStatus 或其子類
public boolean supportsParameter(MethodParameter parameter) {
  return parameter.getParameterType() == SessionStatus.class;
}

23 = {UriComponentsBuilderMethodArgumentResolver@6915}

// 參數類型是 UriComponentsBuilder 或 ServletUriComponentsBuilder
public boolean supportsParameter(MethodParameter parameter) {
  Class<?> parameterType = parameter.getParameterType();
  return (parameterType == UriComponentsBuilder.class)  || (parameterType == ServletUriComponentsBuilder.class);
}

24 = {PrincipalMethodArgumentResolver@6916}

// 參數類型是 Principal 或其子類
public boolean supportsParameter(MethodParameter parameter) {
  return Principal.class.isAssignableFrom(parameter.getParameterType());
}

25 = {RequestParamMethodArgumentResolver@6917}

/*
 * 滿足以下四類條件中的任意一類:
 *   1. 參數標有 @RequestParam 註解、且類型不是 Map 或其子類
 *   2. 參數標有 @RequestParam 註解、且類型是 Map 或其子類、且 @RequestParam 註解的"name"或"value"屬性不為空
 *   3. 參數類型是 MultipartFile 或 Part(包括它們的數組或集合)、且沒有標 @RequestPart 註解
 *   4. 參數類型不簡單、且沒有標 @RequestPart 註解
 */
public boolean supportsParameter25(MethodParameter parameter) {
  if (parameter.hasParameterAnnotation(RequestParam.class)) {
    if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
      RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
      return (requestParam != null && StringUtils.hasText(requestParam.name()));
    } else {
      return true;
    }
  } else {
    if (parameter.hasParameterAnnotation(RequestPart.class)) {
      return false;
    }
    parameter = parameter.nestedIfOptional();
    if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
      return true;
    } else {
      return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
    }
  }
}

26 = {ServletModelAttributeMethodProcessor@6918}

/*
 * 滿足以下兩個條件中的任意一個:
 *   1. 參數標有 @ModelAttribute 註解
 *   2. 參數類型不簡單
 */
public boolean supportsParameter(MethodParameter parameter) {
  return (parameter.hasParameterAnnotation(ModelAttribute.class)) || (!BeanUtils.isSimpleProperty(parameter.getParameterType()));
}

逐個分析可知,案例中介面的參數:

  • request:其類型是 HttpServletRequest,即 Servlet 的子類,因此會被15號解析器ServletRequestMethodArgumentResolver處理。
  • params:其類型是 Map,且標有未設置屬性的 @RequestParam 註解,因此會被1號解析器RequestParamMapMethodArgumentResolver處理。
  • webUser:該參數不是簡單類型,且沒有標任何註解,只能被最後一個解析器ServletModelAttributeMethodProcessor處理。
    接下來調用各自的 resolveArgument 方法即獲得參數值
    ... ...

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 同步與非同步 用來表達任務的提交方式 同步: 提交完任務之後原地等待任務的返回結果 期間不做任何事 非同步: 提交完任務之後不願地等待任務的返回結果 直接去做其他事 有結果自動通知 阻塞與非阻塞 用來表達任務的執行狀態 阻塞 程式處於阻塞態 非阻塞 程式處於就緒態、運行態 綜合使用 同步阻塞 提交任務之 ...
  • JSP頁面的基本結構 在傳統的html頁面文件中加入Java程式片和JSP標記就構成了一個JSP頁面,一個JSP頁面可由5種元素構成: 普通的HTML標記和JavaScript標記 JSP標記,如指令標記、動作標記 變數和方法的聲明 Java程式片 Java表達式 執行過程 當Tomcat伺服器上的 ...
  • 🏵️前言 以下我要講解的是Python中一些重要的內置函數,其中比較重要的會詳細講解,比較簡單的會直接結合代碼進行剖析 🍁一、globals()和locals()內置函數 基於字典的形式獲取局部變數和全局變數 globals()——獲取全局變數的字典 locals()——獲取執行本方法所在命名空 ...
  • 本篇文章繼續介紹 Yarn Application 中 ApplicationMaster 部分的編寫方法。 一、Application Master 編寫方法 上一節講了 Client 提交任務給 RM 的全流程,RM 收到任務後,由 ApplicationsManager 向 NM 申請 Con ...
  • 目錄 一.google angle 簡介 1.ANGLE 支持跨平臺 2.ANGLE 支持渲染器 3.ANGLE 下載地址 二.EGL 坐標系 三.猜你喜歡 零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL ES 學習路 ...
  • 在上一篇中通過閱讀Seata服務端的代碼,我們瞭解到TC是如何處理來自客戶端的請求的,今天這一篇一起來瞭解一下客戶端是如何處理TC發過來的請求的。要想搞清楚這一點,還得從GlobalTransactionScanner說起。 啟動的時候,會調用GlobalTransactionScanner#ini ...
  • java8 (jdk 1.8) 新特性 ——初步認識 1. 什麼是lambda? 目前已知的是,有個箭頭 -> 說一大段官方話,也沒有任何意義 我們直接看代碼: 之前我們創建線程是這樣的 Runnable runnable = new Runnable() { @Override public vo ...
  • 1./*(註釋內容,不做編譯)*/ 2.//(註釋內容) 3.“#”不能忘; 4.scanf中的取地址符不能忘帶; 5.如在scanf中輸入轉義符號外的文字或其他,則編譯出的控制台輸入時必須把輸入的其他原樣再輸入一遍;(否則如圖二) 6.printf中別慣性加了取地址符; 7.分數表示時需特別註意( ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...