MVC之前的那點事兒系列(2):HttpRuntime詳解分析(上)
文章內容
從上章文章都知道,asp.net是執行在HttpRuntime裡的,但是從CLR如何進入HttpRuntime的,可能大家都不太清晰。本章節就是通過深入分析.Net4的原始碼來展示其中的重要步驟。請先看下圖:
首先,CLR在初始化載入的時候,會載入一個非常重要的類AppManagerAppDomainFactory,這個類是做什麼用的呢?首先這個類繼承了IAppManagerAppDomainFactory介面,而這個介面是是有個可供COM呼叫的Create方法,程式碼如下:
[ComImport, Guid("02998279-7175-4d59-aa5a-fb8e44d4ca9d"), System.Runtime.InteropServices.InterfaceTypeAttribute(System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIUnknown)] public interface IAppManagerAppDomainFactory { #if !FEATURE_PAL // FEATURE_PAL does not enable COM [return: MarshalAs(UnmanagedType.Interface)] #else // !FEATURE_PALObject Create(String appId, String appPath); #endif // !FEATURE_PAL Object Create([In, MarshalAs(UnmanagedType.BStr)] String appId, [In, MarshalAs(UnmanagedType.BStr)] String appPath); void Stop(); }
我們來細看一下這個AppManagerAppDomainFactory是如何實現這個介面的,首先該類在預設的建構函式裡,獲取了一個ApplicationManager的例項用於在Create方法裡使用。程式碼如下:
[SecurityPermission(SecurityAction.Demand, Unrestricted=true)] public AppManagerAppDomainFactory() { _appManager = ApplicationManager.GetApplicationManager(); _appManager.Open(); }
回到實現介面的Create方法,我們來看最重要的3行程式碼:
ISAPIApplicationHost appHost = new ISAPIApplicationHost(appId, appPath, false /*validatePhysicalPath*/); ISAPIRuntime isapiRuntime = (ISAPIRuntime)_appManager.CreateObjectInternal(appId, typeof(ISAPIRuntime), appHost, false /*failIfExists*/, null /*hostingParameters*/); isapiRuntime.StartProcessing();
程式碼的主要作用,就是通過ApplicationManager的CreateObjectInternal一系列操作,最終獲取ISAPIRuntime的例項,然後讓非託管程式碼呼叫。所以說CreateObjectInternal方法在這裡發揮了至關重要的功能:建立AppDomain,建立HostingEnvironment等一系列操作。
首先來看看AppManagerAppDomainFactory的建構函式,其裡面呼叫的ApplicationManager. GetApplicationManager()方法是一個單例的實現,程式碼如下:
public static ApplicationManager GetApplicationManager() { if (_theAppManager == null) { lock (_applicationManagerStaticLock) { if (_theAppManager == null) { if (HostingEnvironment.IsHosted) _theAppManager = HostingEnvironment.GetApplicationManager(); if (_theAppManager == null) _theAppManager = new ApplicationManager(); } } } return _theAppManager; }
從程式碼看,大家可能有點疑惑,為什麼HostingEnvironment屬性IsHosted為true的時候會呼叫它的靜態方法GetApplicationManager()來獲取ApplicationManager的例項,這是因為ApplicationManager在後續的步驟建立HostingEnvironment物件並初始化的時候,將this自動傳遞給了HostingEnvironment物件例項(稍後在細說這個事情)。
回頭再來看ApplicationManager例項的CreateObjectInternal方法,部分程式碼如下:
// get hosting environment HostingEnvironment env = GetAppDomainWithHostingEnvironment(appId, appHost, hostingParameters); // create the managed object in the worker app domain // When marshaling Type, the AppDomain must have FileIoPermission to the assembly, which is not // always the case, so we marshal the assembly qualified name instead ObjectHandle h = env.CreateWellKnownObjectInstance(type.AssemblyQualifiedName, failIfExists); return (h != null) ? h.Unwrap() as IRegisteredObject : null;
通過程式碼我們可以看到,首先要先得到HostingEnvironment的例項,然後通過該例項的CreateWellKnownObjectInstance方法返回上述Create方法需要的ISAPIRuntime的例項。那我們應該能想到GetAppDomainWithHostingEnvironment有2個作用,其一是先要獲取AppDomain,其二是獲取HostingEnvironment例項,來看看程式碼是否如我們猜想的結果,先來看程式碼:
private HostingEnvironment GetAppDomainWithHostingEnvironment(String appId, IApplicationHost appHost, HostingEnvironmentParameters hostingParameters) { LockableAppDomainContext ac = GetLockableAppDomainContext (appId); lock (ac) { HostingEnvironment env = ac.HostEnv; if (env != null) { try { env.IsUnloaded(); } catch(AppDomainUnloadedException) { env = null; } } if (env == null) { env = CreateAppDomainWithHostingEnvironmentAndReportErrors(appId, appHost, hostingParameters); ac.HostEnv = env; Interlocked.Increment(ref _accessibleHostingEnvCount); } return env; } }
程式碼告訴我們,首先會檢查是否會有已經存在的AddDomain以及相應的HostingEnvironment例項,如果有返回,沒有就會建立一個新的。通過輾轉呼叫,最終來到一個私有方法CreateAppDomainWithHostingEnvironment,在這個300行的私有方法裡,有我們所迷惑已久的東西。
首先會有關於信任級別的程式碼,比如是執行在FullTrust上還是MiddleTrust上,這裡會有相應的處理程式碼,由於我們這次程式碼分析的重點不在這裡,所以具體程式碼就不細說了,來看看我們需要知道的程式碼段:
// Create the app domain AppDomain appDomain = null; // 此處省略很多程式碼 if (isLegacyCas) { appDomain = AppDomain.CreateDomain(domainId, #if FEATURE_PAL // FEATURE_PAL: hack to avoid non-supported hosting features null, #else // FEATURE_PAL GetDefaultDomainIdentity(), #endif // FEATURE_PAL setup); } else { appDomain = AppDomain.CreateDomain(domainId, #if FEATURE_PAL // FEATURE_PAL: hack to avoid non-supported hosting features null, #else // FEATURE_PAL GetDefaultDomainIdentity(), #endif // FEATURE_PAL setup, permissionSet, fullTrustAssemblies /* fully trusted assemblies list: null means only trust GAC assemblies */); }
通過程式碼可以看出,這就是傳說中建立AppDomain的地方,後續所有的東西比如HttpRuntime, HttpContext都是依託於這個AppDomain,這就是為什麼HttpContext為什麼不能在多站點共享,而能安全存在於AppDomain的原因。
繼續往下看,在建立AppDomain的程式碼之後有幾行這樣的程式碼:
Type hostType = typeof(HostingEnvironment); String module = hostType.Module.Assembly.FullName; String typeName = hostType.FullName; ObjectHandle h = null; // 此處省略很多程式碼 h = Activator.CreateInstance(appDomain, module, typeName); // 此處省略很多程式碼 HostingEnvironment env = (h != null) ? h.Unwrap() as HostingEnvironment : null; // 此處省略很多程式碼 if (appDomainStartupConfigurationException == null) { env.Initialize(this, appHost, configMapPathFactory, hostingParameters, policyLevel); } else { env.Initialize(this, appHost, configMapPathFactory, hostingParameters, policyLevel, appDomainStartupConfigurationException); } return env;
這就是建立HostingEnvironment例項的地方,建立例項以後,緊接著會呼叫Initialize方法來進行初始化,然後返回物件例項(注意該方法的第一個引數哦,是this,也就是ApplicationManager例項自身,就解釋了上面我所說的那個為什麼能通過HostingEnvironment的靜態方法GetApplicationManager()來獲取ApplicationManager例項了)。
通過這些程式碼,我們就可以簡單的知道了,如何獲取ISAPIRuntime例項,從而為進入HttpRuntime做準備了。但是,我依然好奇HostingEnvironment的Initialize初始化方法到底都做了什麼,好吧,我們來看看。
OK,瞄到了一行重要的程式碼:
// initiaze HTTP-independent features HttpRuntime.InitializeHostingFeatures(hostingFlags, policyLevel, appDomainCreationException);
該程式碼進入HttpRuntime的靜態方法,接著呼叫HostingInt方法進行一些初始化工作,其中有一行程式碼也是我們需要知道的,如下:
// Initialize the build manager BuildManager.InitializeBuildManager();
該BuildManager的InitializeBuildManager方法,會呼叫自己的Initialize方法進行初始化另外一些工作,其中包括編譯App_Code目錄下所有的.NET原始碼。由上面的一系列介紹我們知道,在一個AppDomain內,只有一個HostringEnvironment,所以該這個BuildManager的Initialize也就只執行一次,從而保證了編譯不出問題(原始碼的註釋也是這麼說的哦)。
另外HostingInit方法裡在初始化失敗的時候,在catch裡有一行非常特殊的程式碼:
_hostingInitFailed = true;
這是說在建立HostingEnvironment失敗的時候,會給HttpRuntime的HostingInitFailed賦值為True。後面的章節所討論的PreApplicationStartMethodAttribute的概念和WebActivator的入口都和這個值有關係,現在先不做討論,後面章節細再說。
好了,回到AppManagerAppDomainFactory的Create方法,在得到ISAPIRuntime的例項,並且執行StartProcessing方法以後,會返回一個ObjectHandle物件給非託管程式碼,其中包括了ISAPIRuntime的例項,程式碼如下:
return new ObjectHandle(isapiRuntime);
非託管程式碼接受ObjectHandle物件以後,要幹什麼呢?我們且看下篇文章的繼續分析。
同步與推薦
MVC之前的那點事兒系列文章,包括了原創,翻譯,轉載等各型別的文章,如果對你有用,請推薦支援一把,給大叔寫作的動力。
相關推薦
MVC之前的那點事兒系列(2):HttpRuntime詳解分析(上)
文章內容 從上章文章都知道,asp.net是執行在HttpRuntime裡的,但是從CLR如何進入HttpRuntime的,可能大家都不太清晰。本章節就是通過深入分析.Net4的原始碼來展示其中的重要步驟。請先看下圖: 首先,CLR在初始化載入的時候,會載入一個非常重要的類AppManagerApp
MVC之前的那點事兒系列(3):HttpRuntime詳解分析(下)
文章內容 話說,經過各種各樣複雜的我們不知道的內部處理,非託管程式碼正式開始呼叫ISPAIRuntime的ProcessRequest方法了(ISPAIRuntime繼承了IISPAIRuntime介面,該介面可以和COM進行互動,並且暴露了ProcessRequest介面方法)。至於為什麼要呼叫這個方法,
MVC之前的那點事兒系列(8):UrlRouting的理解
文章內容 根據對Http Runtime和Http Pipeline的分析,我們知道一個ASP.NET應用程式可以有多個HttpModuel,但是隻能有一個HttpHandler,並且通過這個HttpHandler的BeginProcessRequest(或ProcessRequest)來處理並返回請求,前
MVC之前的那點事兒系列(1):進入CLR
MVC之前的那點事兒系列,是筆者在2012年初閱讀MVC3原始碼的時候整理的,主要講述的是從HTTP請求道進入MVCHandler之前的內容,包括了原創,翻譯,轉載,整理等各型別文章,當然也參考了部落格園多位大牛的文章,對此表示感謝,這次有時間貼出來,希望對大家有用。 主要內容 本文講解的是:伺服器接受H
MVC之前的那點事兒系列(7):WebActivator的實現原理詳解
文章內容 上篇文章,我們分析如何動態註冊HttpModule的實現,本篇我們來分析一下通過上篇程式碼原理實現的WebActivator類庫,WebActivator提供了3種功能,允許我們分別在HttpApplication初始化之前,之後以及ShutDown的時候分別執行指定的程式碼,示例如下: [
MVC之前的那點事兒系列(9):MVC如何在Pipeline中接管請求的?
文章內容 上個章節我們講到了,可以在HttpModules初始化之前動態新增Route的方式來自定義自己的HttpHandler,最終接管請求的,那MVC是這麼實現的麼?本章節我們就來分析一下相關的MVC原始碼來驗證一下我們的這個問題。 先建立一個MVC3的Web Application,選擇預設的模
MVC之前的那點事兒系列(4):Http Pipeline詳細分析(上)
文章內容 繼續上一章節的內容,通過HttpApplicationFactory的GetApplicationInstance靜態方法獲取例項,然後執行該例項的BeginProcessRequest方法進行執行餘下的Http Pipeline 操作,程式碼如下: // Get application i
MVC之前的那點事兒系列(5):Http Pipeline詳細分析(下)
文章內容 接上面的章節,我們這篇要講解的是Pipeline是執行的各種事件,我們知道,在自定義的HttpModule的Init方法裡,我們可以新增自己的事件,比如如下程式碼: public class Test : IHttpModule { public void Init(HttpAp
MVC之前的那點事兒系列(6):動態註冊HttpModule
文章內容 通過前面的章節,我們知道HttpApplication在初始化的時候會初始化所有配置檔案裡註冊的HttpModules,那麼有一個疑問,能否初始化之前動態載入HttpModule,而不是隻從Web.config裡讀取? 答案是肯定的, ASP.NET MVC3釋出的時候提供了一個Microsof
MVC之前的那點事兒系列(10):MVC為什麼不再需要註冊萬用字元(*.*)了?
文章內容 很多教程裡都提到了,在部署MVC程式的時候要配置萬用字元對映(或者是*.mvc)到aspnet_ISPAI.dll上,在.NET4.0之前確實應該這麼多,但是.NET4.0之後已經不要再費事了,因為它預設就支援了。 你可以會問,沒有對映配置,請求這麼可能會走到aspnet_ISPAI.dll
MVC之前的那點事兒 ---- 系列文章
需要 cnblogs post omx pip 實現原理 內容 activator div MVC之前的那點事兒系列,是筆者在2012年初閱讀MVC3源碼的時候整理的,主要講述的是從HTTP請求道進入MVCHandler之前的內容,包括了原創,翻譯,轉載,整理等各類型文
SpringBoot 2.0 系列(二):流程詳解(上)
寫在前面 本節將詳細介紹如何使用Spring Boot。它涵蓋了諸如專案管理及自動構建工具、自動配置以及如何執行應用程式等主題。我們還介紹了一些Spring Boot最佳實踐。Spring Boot沒有什麼特別之處(它只是另一個我們可以使用的庫),但是有一些約
解讀ASP.NET 5 & MVC6系列(6):Middleware詳解
在第1章專案結構分析中,我們提到Startup.cs作為整個程式的入口點,等同於傳統的Global.asax檔案,即:用於初始化系統級的資訊(例如,MVC中的路由配置)。本章我們就來一一分析,在這裡如何初始化這些系統級的資訊。 新舊版本之間的Pipeline區別 ASP.NET 5和之前版本的最大區別是對HT
EventBus原始碼分析(四):執行緒模型分析(2.4版本)
EventBus有四種執行緒模型 PostThread模式不需執行緒切換,直接在釋出者執行緒進行事件處理。 MainThread模式分類討論:釋出者執行緒是主執行緒則直接呼叫事件處理方法,否則通過Handler進行執行緒切換,切換到主執行緒處理事件,該模
前端基礎進階(九):圖例詳解那道setTimeout與迴圈閉包的經典面試題
配圖與本文無關 我在詳細圖解作用域鏈與閉包一文中的結尾留下了一個關於setTimeout與迴圈閉包的思考題。 利用閉包,修改下面的程式碼,讓迴圈輸出的結果依次為1, 2, 3, 4, 5 for (var i=1; i<=5; i++) { setTimeout( function ti
算法筆記(七):復雜度分析(一)
n+1 增長 角度 復雜 判斷 and 就是 ret 執行時間 (一)漸進符號(這裏暫時只考慮大O) 以輸入規模n為自變量建立的時間復雜度實際上還是較復雜的,例如an2+bn+c+1,不僅與輸入規模有關,還與系統a、b和c有關。此時對該函數進一步抽象,僅考慮運行時間的
java基礎學習總結(二十五):logback詳解
為什麼使用logback logback大約有以下的一些優點: 核心重寫、測試充分、初始化記憶體載入更小,這一切讓logback效能和log4j相比有諸多倍的提升 logback非常自然地直接實現了slf4j,這個嚴格來說算不上優點,只是這樣,再理解slf4j的前提下會很容易理解
Dubbo(概念篇):Dubbo 詳解,架構演變及優缺點
架構演變 單一應用框架(ORM) 當網站流量很小時,只需一個應用,將所有功能如下單支付等都部署在一起,以減少部署節點和成本。 缺點:單一的系統架構,使得在開發過程中,佔用的資源越來越多,而且隨著流量的增加越來越難以維護。 垂直應用框架(MVC) 垂直應用架構解決了單一應用架
C語言面向物件程式設計(二):繼承詳解
C++ 中的繼承,從派生類與基類的關係來看(出於對比 C 與 C++,只說公有繼承): 派生類內部可以直接使用基類的 public 、protected 成員(包括變數和函式) 使用派生類的物件,可以像訪問派生類自己的成員一樣訪問基類的成員 對於被派生
zookeeper入門學習(二):原理詳解
一 .Zookeeper功能簡介 ZooKeeper 是一個開源的分散式協調服務,由雅虎建立,是 Google Chubby 的開源實現。 分散式應用程式可以基於 ZooKeeper 實現諸如資料釋出/訂閱、負載均衡、命名服務、分散式協 調/通知