主頁 > 後端開發 > 面試高頻SpringMVC執行流程最優解(原始碼分析)

面試高頻SpringMVC執行流程最優解(原始碼分析)

2020-09-29 10:55:38 後端開發

文章已托管到GitHub,大家可以去GitHub查看閱讀,歡迎老板們前來Star!

搜索關注微信公眾號 碼出Offer 領取各種學習資料!

SpringMVC執行流程
SpringMVC概述

Spring MVC屬于SpringFrameWork的后續產品,已經融合在Spring Web Flow里面,Spring 框架提供了構建 Web 應用程式的全功能 MVC 模塊,使用 Spring 可插入的 MVC 架構,從而在使用Spring進行WEB開發時,可以選擇使用Spring的Spring MVC框架或集成其他MVC開發框架,

SpringMVC執行流程概括

SpringMVC框架固然強大,但是其執行流程更是妙不可言,所以我們這次要用一個簡單的例子去深究一下SpringMVC的底層執行流程!

如下是SpringMVC的執行流程梗概圖,我會在后面的底層流程剖析中重點提到梗概圖中的這幾個零件,以及它們的作用!


SpringMVC執行流程梗概圖(切記:該圖只是梳理思路,并不特別嚴謹,請諒解)
springMVC執行流程
springMVC執行流程
SpringMVC的重要組件(可視化組件)

既然,我們要選擇剖析SpringMVC底層執行流程,那肯定是要先分析我們能所看到表面的MVC重要組件,這樣我們分析完可視組件后,就能找到分析SpringMVC底層執行流程的入口,所以分析它的重要組件顯得更是重要!

SpringMVC的重要組件是由核心的前端控制器(web.xml)后端控制器(Controller)spring-mvc.xml組態檔組成,

  • 核心的前端控制器: 作為MVC框架,首先要解決的就是如何能收到請求,所以MVC框架大都會設計一款前端控制器(入口或者說起點),選型在Servlet或Filter兩者之一,由前端控制器來最率先的作業,接收請求,在SpringMVC中,也不例外,前端控制器的選型確定為Servlet(DispatcherServlet),此前端控制器在接收請求后,還會負責SpringMVC的核心調度管理,所以既是前端又是核心,
  • 后端控制器: 后端控制器為Controller,等價于之前定義的Servlet,MVC框架中,后端控制器也是必不可少的重要組件之一,因為它接收了用戶請求的大量資料引數物件(或Json)存盤在域中方便頁面(JSP)取值,或是攜帶著這些資料回傳所需要跳轉(重定向或請求轉發)的頁面,這里值得注意的是,后端控制器本質并不是一個普通的Servlet,也不是BaseServlet,它只是一個普通的類,里面卻像曾經的BaseServlet一樣可以擁有很多個方法,這些方法在SpringMVC中成為一個個Handler(換湯不換藥,本質仍然),所以在MVC模式的執行流程環節中,后端控制器控制著頁面的跳轉和資料的傳遞,在這里也有著很高的地位,
  • spring-mvc.xml組態檔: 該組態檔配置著許多在執行程序中需要加載的組件,比如:注解掃描器、注解掃描驅動、試圖決議器、靜態資源處理器、例外決議器、攔截器、上傳決議器等等,如果我們要使用這些組件,就需要在該組態檔中注入這些組件的相關配置,注入配置后由SpringMVC工廠在執行程序中加載這些組件,以達成我們使用這些組件的目的,所以這也是它受人青睞的原因,
SpringMVC執行流程剖析

上述得知,我們執行流程剖析的入口既是核心的前端控制器,即web.xml,那我們有資格了解該前端控制器中配置了什么!如下:

前端控制器
image-20200719185840281
image-20200719185840281

由上圖所知,前端控制器中所包含的即是同時啟動SpringMVC工廠和Spring工廠,讓兩個工廠同時運作處理請求,并作出回應,既然要剖析SpringMVC的底層執行流程,那我們要從加載SpringMVC工廠的DispatcherServlet說起,首先進入到DispatcherServlet中,查看源代碼所有方法,如下圖所示:

DispatcherServlet原始碼所有方法
image-20200719190557728
image-20200719190557728
DispatcherServlet繼承FrameworkServlet
image-20200719190959184
image-20200719190959184

上圖所示,我進入到了DispatcherServlet中,既然說它是一個Servlet,那肯定是需要尋找它的service方法,因為Service方法是Servlet的核心所在,于是我打開了IDEA的方法串列搜索service方法,未果,雖然未果,但是我發現兩個重要的線索,一是該Servlet中有一個doSerivce方法,二是DispatcherServlet繼承了FrameworkServlet,我想既然子類沒有service方法,父類肯定有,于是我進入到了FrameworkServlet查看源代碼,如下圖所示:

FrameworkServlet原始碼
image-20200719193120903
image-20200719193120903

我興沖沖在父類(FrameworkServlet)中找到了service方法,但是還是感覺高興的太早了,該service方法中除了resolve方法獲取請求方式和processRequest方法外,我一無所知,隨后竟然發現了紅色箭頭所指向的東西super.service(request, response);,這意味著什么呢?這意味著它繼承了父類擁有的service方法,于是我點擊super句點后面的service方法查看原始碼驚人的發現這個類竟然是HttpServlet,顯然我們找service方法的這條路走到盡頭了,在里面有兩個方法存在一個是resolve方法,它是獲取請求方式的,還有一個方法不知道是做什么的,于是我點擊了進去查看原始碼,如下圖所示:

processRequest方法原始碼
image-20200719193708541
image-20200719193708541

既然我們進去看到了processRequest方法的原始碼,就要找重要的方法,何為重要的方法呢,一般被try塊包裹的方法必然是重要方法,于是我找到了doService(request, response);方法,并繼續點擊去看該doService方法的原始碼,如下圖所示:

doService(request, response);方法原始碼
image-20200719194003105
image-20200719194003105

逐漸失去耐心的我真的被驚訝到了,進入到doService方法后,也沒有跳到其他的類中,而卻還是在該類中跳到了一個空的doService();方法中,唉,探究究竟真的是件不容易的事情呀~我嘆了一口氣,冷靜下來一想,父類是空方法沒有實作,那核心邏輯代碼必定是在子類中了呀,這不是多型嘛!于是,我得出了結論,費勁吧難,找入口的邏輯代碼回過頭來還是得看DispatcherServlet中的那個doService方法,此時我知道,這必將是一個漫長的探索之路,于是,我秉著探究原理的心態,再一次點進了被我錯過的那個DispatcherServlet中的doSerivce方法,如下圖:

DispatcherServlet中的doService()方法
image-20200719195305154
image-20200719195305154

既然確定了這是探究底層原理的開始,那我們就在doServie()方法中尋找重要的邏輯,于是我再一次的在try塊中找到了一個名為doDispatch(request, response);的方法(省略了前面的各種初始化和存盤域資料),在探究底層原理的道路上,你會發現越來越接近真理,雖然這注定是一個漫長的探索程序,我也情愿,于是,點擊進入到了doDispatch()方法中的原始碼,如下圖所示:

doDispatch()方法原始碼
image-20200719200052667
image-20200719200052667

走進了doDispatch()方法的原始碼,才知道我沒有看錯你,里面標有注釋的都是一些重要的執行邏輯方法,接下來我們會一個個的分析,逐步深入理解SpringMVC的執行流程,既然探索執行流程那就少不了Debug(Debug除錯功能,Debug能很清晰的看到執行流程),于是我在getHandler()方法的那一行打了一個斷點,下一步跟進執行流程進入到了getHandler()方法,如下圖所示:

getHandler方法原始碼(注釋解釋:為當前請求尋找并回傳一個handler物件)
image-20200719201557155
image-20200719201557155

斷點停留到了這一行,因為getHandler()的名字,顧名思義就是獲取Controller層中的Handler,它是怎么獲取到的呢?我們在斷點的變數顯示框中,看到handlerMappings是一個陣列,其中有三個物件,他們可以分別以不同的方式處理不同的Handler,其中我們可以點擊這個三個物件,一一把其物件展開查看重要屬性,如下圖所示:

0 = {RequestMappingHandlerMapping}
image-20200719202203776
image-20200719202203776
2 = {SimpleUrlHandlerMapping}
image-20200719202458924
image-20200719202458924

如上圖得知,RequestMappingHandlerMapping物件識別了我們Controller中的@RequestMapping注解和各個Handler上方的注解路徑,SimpleUrlHandlerMapping物件識別了處理靜態資源驅動所創建的那個默認Servlet,而處理靜態資源的默認Servlet路徑給了/**,它識別了這個路徑,HanderMapping映射器中的物件,通過注解識別獲取到了Controller層的各個Handler請求路徑注解后,就執行到了下一行,如下圖:

getHandler方法原始碼
image-20200719203457220
image-20200719203457220

通過注解可以找到所有的Handler,其中所有的Handler就存盤在handlerMappings中,于是它就遍歷了此物件,隨后根據各自的請求物件獲取對應的Handler并判慷訓傳獲取到的對應Handler物件,繼續向下執行,你還會發現這么一個東西,如下圖:

getHandler方法
image-20200719204645324
image-20200719204645324

對,你會發現即將回傳的Handler是一個名為HandlerExecutionChain的執行鏈,其中執行鏈內包含了即將回傳的handler物件和一個interceptorList集合,其中集合內有兩個物件,這兩個物件就是攔截器,所以,不管是你自己使用了攔截器還是沒有使用攔截器(內部底層有攔截器),這些攔截器和handler物件會以一個鏈條的形式執行(攔截器在前,handler物件在后),則執行程序是遵循著先執行攔截器,后回傳并執行handler物件的順序,回傳了HandlerExecutionChain執行鏈,那么就要開始執行執行鏈了!問題來了,究竟是誰依次執行攔截器和handler物件呢?如下圖:

doDispatch()方法原始碼
image-20200719210132545
image-20200719210132545

回傳執行鏈后,繼續執行就執行到了這一行代碼,其注釋解釋為為當前請求物件尋找一個handler配接器,如果你學過配接器設計模式也許你會更容易理解,沒有學過也沒有關系,隨后的解釋你也可以理解的,知道了它要為請求物件尋找配接器,那么我們繼續執行,就得到了如下啊資訊:

getHandlerAdapter方法原始碼
image-20200719210604037
image-20200719210604037

執行流程進入到了getHandlerAdapter方法,遠遠看到這個方法有一種似曾相識的感覺,對,它和HandlerMapping映射器很像,簡直就是孿生兄弟,該方法要根據當前回傳的handler物件,為其handler物件尋找一個配接器,而handlerAdapters集合物件中就存盤著三個配接器,想想我們在映射器中獲取執行鏈的時候是不是也三個呢?對的,他們是成對出現的,handler的物件找其對應的配接器才可以繼續執行下去,找到與當前handler物件成對的配接器之后,就回傳了該配接器,配接器回傳后中間經過了如下方法:

doDispatch()方法原始碼
image-20200719211355102
image-20200719211355102

中間經過了這一段代碼,獲取了請求物件的請求方式并對此進行了一系列的判斷操作,繼續執行到了下面,下面有一個if判斷,判斷執行了applyPreHandler方法,此方法就是攔截器的前置方法,執行完攔截器的前置方法后,繼續向下執行,這時候就該執行如下代碼:

doDispatch()方法原始碼
image-20200719211927390
image-20200719211927390

從此方法可見ha物件是此時的handler物件,說明在執行handler物件之前執行了攔截器,這也是遵循了執行鏈的順序,繼續執行下去,將完成了請求引數物件的封裝和回應中Json字串與物件的轉換后,回傳了一個mv物件,那么mv物件是什么呢?其實是在上面定義的ModelAndView物件,回傳mv物件后,繼續執行便執行到了如下重要的執行邏輯:

doDispatch()方法原始碼
image-20200719213149039
image-20200719213149039

其中在執行程序中,判斷并執行了攔截器的后置方法,執行完后置方法后,進行了一系列的判斷,就開始執行了processDispatchResult(processdRequest, response, mappdeHandler, mv, dispatchException)方法,該方法中攜帶了請求物件、回應物件、handler物件、ModelAndView物件等,進入到此方法原始碼中,你會發現他進行了一系列的判斷,通過如下方法對ModelAndView物件進行了渲染:

render方法原始碼
image-20200719213629854
image-20200719213629854

對ModelAndView物件進行渲染和視圖決議后,繼續跟進方法,因為勝利馬上就要來臨了,如下圖:

render方法原始碼
image-20200719213959635
image-20200719213959635

繼續執行,就會發現它開始通過resolveViewName方法來決議視圖了,于是,就進入到了該方法,如下圖:

resolveViewName方法原始碼
image-20200719214336328
image-20200719214336328

首先,看到此方法的原始碼,你可以發現,viewResolvers視圖決議器會決議ModelAndView物件,并回傳了一個View物件,后來View物件也會被一個名叫render的方法渲染,如下:

view.render()

可見,此View物件并不簡單,它執行了一番過后,由于我的網頁跳轉時使用的請求轉發,于是就到了如下頁面原始碼:

InternalResourceView原始碼

點擊此方法就會發現我們熟悉的請求轉發了,此時它在這里讀取決議了spring-mvc.xml組態檔,為內部默認的請求轉發拼接好了路徑forward:/XXX/XXX(此時也決議了spring-mvc.xml組態檔內的其他組件),如下圖:

請求轉發(InternalResourceView.java)
image-20200719215453276
image-20200719215453276

如果是重定向呢,那么就是如下類中的重定向方法,如下圖所示:

重定向(RedirectView.java)
image-20200719215921537
image-20200719215921537

隨后,轉發或重定向跳轉至JSP頁面(視圖層)后,渲染資料到HTML中,并渲染完HTML內容后,輸出給瀏覽器并作出回應,在瀏覽器中顯示!

此SpringMVC我以打斷點除錯的方式走了一遍底層的執行流程,我相信你自己打斷點除錯也會有一個不錯的識訓!

SpringMVC的內部組件
  • HandlerMapping(處理器映射器)
  • HandlerAdapter(處理器配接器)
  • ViewResolver(視圖決議器)
SpringMVC默認組件初始化加載

上面我們通過Debug簡單的走了一遍SpringMVC的執行流程,但是前面所說的那么多內部組件是怎么來的呢?于是,我從DispatherServlet找到了一個方法initStrategies,如下:

initStrategies方法原始碼
image-20200720214203411
image-20200720214203411

在執行流程開始之前,做了內部組件的一系列初始化操作,這里我們以initHandlerMappings方法進行追溯,找到 SpringMVC 的默認組態檔,進入 initHandlerMappings 方法,因為我們并沒有進行配置(注解或者 Bean 標簽),所以該方法中的前兩種情況都會跳過,會來到最下面的默認情況處,呼叫了 getDefaultStrategies 方法,讀取默認的組態檔,

initHandlerMappings方法原始碼
image-20200720214539186
image-20200720214539186
getDefaultStrategies方法原始碼
image-20200720214701181
image-20200720214701181

在 getDefaultStrategies 方法中,有一個 defaultStrategies,我們當該類上面看一下,如下圖:

defaultStrategies原始碼
image-20200720214907843
image-20200720214907843

這里就是進行加載默認組態檔的地方,點擊 DEFAULT_STRATEGIES_PATH 常量,找到了默認的配置組態檔,

DEFAULT_STRATEGIES_PATH常量
image-20200720215021940
image-20200720215021940

于是我想辦法翻到了這個組態檔,里面就初始化了各種組件,大家可以查閱:

DispatcherServlet.properties組態檔
image-20200720215320968
image-20200720215320968

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/138063.html

標籤:Java

上一篇:求FVC指紋的DB_a庫

下一篇:Spring專案 openjdk7 行程無回應后jstack命令無法列印thread dump

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more