深入剖析ASP.NET的编译原理之一:动态编译(Dynamical Compilation)
原文:http://www.cnblogs.com/artech/archive/2007/05/21/753620.html
Microsoft 的Visual Studio為我們在應(yīng)用開發(fā)中提供的強(qiáng)大功能,我們是有目共睹。借助該工具,是我們的開發(fā) 顯得更加高效而輕松。從Microsoft把這個(gè)IDE的名字從VS.NET 該為VS(比如原來的Visual Studio.NET 2003,現(xiàn)在的版本叫VS2005),可以MS對該IDE的期望和野心:MS要把它改造成一個(gè)萬能的IDE。不過任何都有其兩面性,對于我們廣大的開發(fā)者來說,VS是我們的各種行為簡單化,傻瓜化;但是在另一方面,他也會蒙蔽我們的眼睛,使我們對它背后做的事情視而不見。以我們的ASP.NET Website開發(fā)為例,編程、編譯、部署都可以借助VS,有了VS一切顯得如此簡單,每個(gè)人都會做,但是我想很多一部分人對一個(gè)ASP.NET Website如何進(jìn)行編譯不會很了解。這篇文章就來談?wù)劚澈蟮墓适隆狝SP.NET是如何進(jìn)行編譯的。由于篇幅的問題整篇文章分兩個(gè)部分:動(dòng)態(tài)編譯Dynamical Compilation和預(yù)編譯(Precompilation)。
1.動(dòng)態(tài)編譯的過程
我們先來介紹在動(dòng)態(tài)編譯下的大體的執(zhí)行流程:當(dāng)ASP.NET收到一個(gè)基于某個(gè)page的request的時(shí)候,先判斷該P(yáng)age和相關(guān)的Source code是否編譯過,如果沒有就將其編譯,如果已經(jīng)編譯,就是用已經(jīng)Load的Assembly直接生成Page對象。
在這里有下面幾點(diǎn)需要注意:
1). 動(dòng)態(tài)編譯是按需編譯的,ASP.NET只會編譯和當(dāng)前Request相關(guān)的aspx和code。
2). 動(dòng)態(tài)編譯是基于某個(gè)目錄的,也就是說ASP.NET會把被請求的page所在的目錄的所有需要編譯的文件進(jìn)行編譯,并生成一個(gè)Assembly。
3). 除了編譯生成的Assembly外,動(dòng)態(tài)編譯還會生成一系列的輔助文件。
4). 對相關(guān)文件的修改,會導(dǎo)致重新編譯,但是修改對當(dāng)前的Request不起作用。也就是說如果你對某個(gè)aspx進(jìn)行修改,那么對于修改后抵達(dá)的Request,會導(dǎo)致重新編譯,但是對于之前的Request使用的依然是原來編譯好的Assembly。
5). 編譯生成的文件被放在一個(gè)臨時(shí)目錄中,這個(gè)目錄的地址為Windows Directory\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files。其具體的目錄結(jié)構(gòu)如下圖所示:
在Temporary ASP.NET Files下的Artech.ASPNETDeployment是IIS中Virtual Directory的名稱,以下兩級目錄的名稱由Hash value構(gòu)成,所以編譯生成的文件就保存在c6f16246目錄下。這個(gè)目錄你可以通過HttpRuntime.CodegenDir獲得。
Directory\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files只是一個(gè)默認(rèn)的臨時(shí)目錄,你可以在web config中的compilation section中設(shè)置你需要的臨時(shí)目錄。
<compilation?tempDirectory="d:\MyTempFiles"?/>2.Sample
現(xiàn)在我用一個(gè)Sample來一探ASP.NET是如何進(jìn)行動(dòng)態(tài)編譯的。
在這個(gè)Sample中,我建立了一個(gè)Website,在根目錄下創(chuàng)建了兩個(gè)Page:Default和Default2。
在兩個(gè)子目錄Part I和Part II下分別創(chuàng)建了兩個(gè)Web page:Page1和Page2。在App_Code目錄中創(chuàng)建了一個(gè)Utility的static class。下面是它的定義:
public?static?class?Utility{
????public?static?string?ReflectAllAssmebly()
????{
????????StringBuilder?refllectionResult?=?new?StringBuilder();
????????foreach?(Assembly?assembly?in?AppDomain.CurrentDomain.GetAssemblies())
????????{
????????????if(!assembly.FullName.Contains("App_Web"))
????????????{
????????????????continue;
????????????}
????????????refllectionResult.Append(assembly.FullName?+?"<br/>");
????????????Type[]?allType?=?assembly.GetTypes();
????????????foreach?(Type?typeInfo?in?allType)
????????????{
????????????????refllectionResult.Append(" "+?typeInfo.Name?+?"<br/>");
????????????}
????????}
????????return?refllectionResult.ToString();
????}
}
內(nèi)容很簡單,對當(dāng)前加載的所有相關(guān)的Assembly(這些Assembly的Fullname以App_Web打頭)進(jìn)行Reflection,列出所有的Type。這個(gè)ReflectAllAssmebly將在5個(gè)Web page(Default?Page和兩隊(duì)Page1&Page2)的Page_Load事件中被調(diào)用。
protected?void?Page_Load(object?sender,?EventArgs?e)????{
????????this.Response.Write(Utility.ReflectAllAssmebly());
}
Default是列出所有4Page對應(yīng)的Link以便我們訪問它們,在我們再進(jìn)行編譯的情況下在IE中輸入對應(yīng)的URL來訪問Default Page。(其他Page的Html中不具有真正的內(nèi)容,是一個(gè)空的page.)
?
通過上面的顯示,我們可以看到現(xiàn)在有一個(gè)Assembly:App_Web_wh7-uda5。該Asssembly定一個(gè)的Type有5個(gè),??_Default和?default_aspx分別對應(yīng)Default Page,而Default2和?default2_aspxDefault2 Page的。FastObjectFactory_app_web_wh7_uda5是很重要的Type,我將會在后面對其進(jìn)行深入介紹。正如我們在上面說過的,動(dòng)態(tài)編譯是按需編譯,現(xiàn)在我們對Default Page進(jìn)行訪問,由于這次對該Website的第一次訪問,所有需要的Source Code,包括aspx,code behind都要進(jìn)行編譯。在這個(gè)Sample中,雖然我們并沒有訪問Default2 page,但是我們說過,動(dòng)態(tài)編譯是基于目錄的,由于Default Page和Default2 Page都直接置于根目錄下,所以ASP.NET會把根目錄下的所有文件編譯到一個(gè)Assembly中。由于Page1和Page2位于子目錄Part I和Part II之下,所以不會參與編譯。除非我們下載對它進(jìn)行Request。
我們現(xiàn)在來訪問Part I下的Page1和Page2看看會有什么結(jié)果。我們會發(fā)現(xiàn),兩次Request獲得的輸出是一樣的:
通過上面的輸出我們發(fā)現(xiàn),當(dāng)前AppDomain中被加載的Assembly多了一個(gè):App_Web_n1mhegpg。我們可以通過定義在該Assembly中的Type的命名可以猜出該Assembly是對Part I 目錄進(jìn)行編譯產(chǎn)生的。Page1和Page2的編譯后的Type name變成了part_i_page1_aspx& Page1和part_i_page2_aspx& Page2。此外我們看到,該Assembly中依然有一個(gè)FastObjectFactory的Type:FastObjectFactory_app_web_n1mhegpg。在這里我需要特別指出的是,名稱的后綴都是通過 Hash算法得到的。
有了上面的理論和實(shí)驗(yàn)結(jié)果,我想這個(gè)時(shí)候,你肯定已經(jīng)想到,如果我現(xiàn)在對Part II的Page1和Page2進(jìn)行訪問,輸出結(jié)果會是什么樣子了。
如果這個(gè)時(shí)候,你查看臨時(shí)目錄(Directory\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files)中該Website對應(yīng)的子目錄,已將會看到生成了一些列的文件。
3.Page最終被轉(zhuǎn)化成什么?
我們現(xiàn)在來看看通過編譯,一個(gè)Page到底轉(zhuǎn)換成什么。我們以Part I/Page1為例。通過上面的Sampe,我們知道它最終變成了兩個(gè)Type:Page1和part_i_page1_aspx。
下面是Page1的定義:
public?class?Page1?:?Page,?IRequiresSessionState{
????//?Fields
????protected?HtmlForm?form1;
????//?Methods
????protected?void?Page_Load(object?sender,?EventArgs?e)
????{
????????base.Response.Write(Utility.ReflectAllAssmebly());
????}
????//?Properties
????protected?HttpApplication?ApplicationInstance
????{
????????get
????????{
????????????return?this.Context.ApplicationInstance;
????????}
????}
????protected?DefaultProfile?Profile
????{
????????get
????????{
????????????return?(DefaultProfile)?this.Context.Profile;
????????}
????}
}
下面是part_i_page1_aspx的定義 [CompilerGlobalScope]
public?class?part_i_page1_aspx?:?Page1,?IHttpHandler
{
????//?Fields
????private?static?object?__fileDependencies;
????private?static?bool?__initialized;
????//?Methods
????public?part_i_page1_aspx();
????private?HtmlHead?__BuildControl__control2();
????private?HtmlTitle?__BuildControl__control3();
????private?HtmlLink?__BuildControl__control4();
????private?HtmlForm?__BuildControlform1();
????private?void?__BuildControlTree(part_i_page1_aspx?__ctrl);
????protected?override?void?FrameworkInitialize();
????public?override?int?GetTypeHashCode();
????public?override?void?ProcessRequest(HttpContext?context);
}
在part_i_page1_aspx中定義了一個(gè)基于aspx的HttpHandler所需的所有操作,并且它繼承了Page1。所以我們可以說Part I/Page1這個(gè)page 最終的體現(xiàn)為part_i_page1_aspx。進(jìn)一步說,對Part I/Page1的Http Request,ASP.NET所要做的就是生成一個(gè)part_i_page1_aspx來Handle這個(gè)request就可以了。?
4.FastObjectFactory
通過上面的一個(gè)簡單的Sample,你已經(jīng)看到每個(gè)Assembly中都會生成一個(gè)以FastObjectFactory作為前綴的Type。這是一個(gè)很重要的Type,從它的名稱我們不難猜出它的作用:高效的生成對象。而生成的是什么樣的對象呢?實(shí)際上就是基于每個(gè)aspx的Http request的Http handler,對于Part I/Page1就是我們上面一節(jié)分析的part_i_page1_aspx對象。
我們現(xiàn)在通過Reflector查看我們生成的App_Web_n1mhegpg中的FastObjectFactory_app_web_n1mhegpg是如何定義的。
internal?class?FastObjectFactory_app_web_n1mhegpg{
????//?Methods
????private?FastObjectFactory_app_web_n1mhegpg()
????{
????}
????private?static?object?Create_ASP_part_i_page1_aspx()
????{
????????return?new?part_i_page1_aspx();
????}
????private?static?object?Create_ASP_part_i_page2_aspx()
????{
????????return?new?part_i_page2_aspx();
????}
}
通過上面的Code,我們可以看到在FastObjectFactory中定義一系列的Create_ASP_XXX(后綴就是Page 編譯生成的Type的名稱)。通過這些方法,可以快速生成對一個(gè)的Page。至于為什么會叫作FastObjectFactory,我想是因?yàn)橹苯油ㄟ^調(diào)用這個(gè)靜態(tài)的方法快速地創(chuàng)建Page對象,從而避免使用Reflection的late binding帶來的性能的影響吧。
5.Preservation Files
進(jìn)行每一次編譯,ASP.NET會生成一系列具有.compiled擴(kuò)展名的保留文件(Preservation File)。這個(gè)文件非常重要,我們現(xiàn)在來深入介紹這個(gè)樣一個(gè)文件。
Preservation File這個(gè)文件本質(zhì)上是一個(gè)XML。它是基于每個(gè)Page的,也就是每個(gè)Page都會有一個(gè)這樣的Preservation File. 無論P(yáng)age對應(yīng)的Directory是怎樣的,與之對應(yīng)的Preservation File總會保存在根目錄下,所以必須有一種機(jī)制保持為處于不同Directory的Page生成的Preservation File具有不同的文件名,不管Page的名稱是否一樣。所以Preservation File采用下面的命名機(jī)制:
[page].aspx.[folder-hash].compiled
其中[page]是Page的名稱,[folder-hash]是對page所在路徑的Hash Value。這樣做有兩個(gè)好處。
- 保證處于同一級目錄的所有Preservation File具有不同的文件名。
- 保證ASP.NET對于一個(gè)Http request可以找到Page對應(yīng)的Preservation File。
下面這個(gè)Preservation File就是上面Sample中Part II/Page1.aspx對應(yīng)的Preservation File,名稱為default2.aspx.cdcab7d2.compiled。我們來看看XML每個(gè)Item各代表什么意思。
<?xml?version="1.0"?encoding="utf-8"?><preserve?resultType="3"?virtualPath="/Artech.ASPNETDeployment/Part?II/Page1.aspx"?hash="fffffff75090c769"?filehash="5f27fa390c45c52a"?flags="110000"?assembly="App_Web_hitlo3dt"?type="ASP.part_ii_page1_aspx">
????<filedeps>
????????<filedep?name="/Artech.ASPNETDeployment/Part?II/Page1.aspx"?/>
????????<filedep?name="/Artech.ASPNETDeployment/Part?II/Page1.aspx.cs"?/>
????</filedeps>
</preserve>
有這段XML我們可以看到,所有的內(nèi)容都包含在preserve XML element中,在他中間定義了幾個(gè)重要的attribute.
- virtualPath: Page的虛擬地址。
- assembly:Assembly名稱
- Type:Page的編譯后對應(yīng)的Type(Http handler)。
- hash: 一個(gè)代表本Preservation File狀態(tài)的Hash value。
- filehash: 一個(gè)代表本Dependent File狀態(tài)的Hash value。
通過hash和filehash的緩存,ASP.NET可以判斷自上一次使用以來,Preservation File和它所依賴的Dependent File是否被改動(dòng),如果真的被改動(dòng),將會重新編譯。因?yàn)閷τ谖募娜魏胃膭?dòng)都會導(dǎo)致該Hash value的改變。
此外,Preservation File的<filedeps>列出了所有依賴的文件,對于Page,一般是aspx和code behind。
6. 進(jìn)一步剖析整個(gè)動(dòng)態(tài)編譯過程
現(xiàn)在我們來總結(jié)整個(gè)動(dòng)態(tài)編譯的過程:
Step1:當(dāng)ASP.NET收到對于某個(gè)Page的Request,根據(jù)這個(gè)request對應(yīng)的Url試著找到該page對應(yīng)的Preservation File,如果沒有找到,重新編譯Page所在目錄下的所有需要編譯的文件,同時(shí)生成一些額外的文件,包括Preservation File。
Step2:然后解析這個(gè)Preservation File,通過hash和filehash判斷文件自身或者是Dependent File是否在上一次編譯之后進(jìn)行過任何的修改,如果是的,則重新編譯。然后重新執(zhí)行Step2。
Step3:通過Preservation File 的assembly attribute Load對應(yīng)的assembly(如果Assembly還沒有被Load的話),通過type獲知對應(yīng)的aspx type,然后借助FastObjectFactory的對應(yīng)的Create_ASP_XXX創(chuàng)建這個(gè)type。這個(gè)新創(chuàng)建的對象就是我們需要的Http Handler,在之上執(zhí)行相應(yīng)的操作把結(jié)果Response到客戶端。
對動(dòng)態(tài)編譯的討論就到這里,在本篇文章下半部分將會討論另一種更加有用的編譯方式:[原創(chuàng)] 深入剖析ASP.NET的編譯原理之二:預(yù)編譯(Precompilation)。
轉(zhuǎn)載于:https://www.cnblogs.com/answercard/archive/2009/01/08/1371599.html
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的深入剖析ASP.NET的编译原理之一:动态编译(Dynamical Compilation)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: VSTS问题二则
- 下一篇: 关于级数∑(x n-x n-1)一致收敛