深入剖析ASP.NET的编译原理之二:预编译(Precompilation)
(轉(zhuǎn)載)在本篇文章的第一部分:[原創(chuàng)]深入剖析ASP.NET的編譯原理之一:動(dòng)態(tài)編譯(Dynamical Compilation),詳細(xì)討論了ASP.NET如何進(jìn)行動(dòng)態(tài)編譯的,現(xiàn)在我們來(lái)談?wù)劻硗庖环N重要的編譯方式:預(yù)編譯(Precompilation)。
1.為什么要進(jìn)行預(yù)編譯
ASP.NET 2.0的編譯方式大體可以分成兩種:動(dòng)態(tài)編譯和預(yù)編譯,要回答為什么要進(jìn)行預(yù)編譯,我們先要看看動(dòng)態(tài)編譯有什么不好的地方。我們回顧一下上一篇介紹的ASP.NET進(jìn)行動(dòng)態(tài)編譯的簡(jiǎn)單的流程:當(dāng)來(lái)自Brower的一個(gè)基于aspx的Http request抵達(dá)Web server,IIS handle這個(gè)request,通過(guò)分析注冊(cè)在IIS中的Application Mapping,將Request 傳給aspnet_isapi.dll ISAPI extension。ISAPI extension通過(guò)HttpRuntime進(jìn)入Http Runtime Pipeline,HttpRuntime為每個(gè)Request創(chuàng)建一個(gè)單獨(dú)的HttpContext對(duì)象,用于保存request的Context信息。在Http Runtime Pipeline中,Http request會(huì)被注冊(cè)的一系列的Http module處理,比如OutputCache Module,Session Module,Authentication Module,Authorization,ErrorHandler Module等等。在Pipeline的終端,ASP.NET需要需要根據(jù)request創(chuàng)建對(duì)應(yīng)的HttpHandler對(duì)象來(lái)處理該Request,并生成結(jié)果Response到Client。對(duì)于一個(gè)基于Aspx的Http request,對(duì)應(yīng)的Http handler對(duì)象一般就是一個(gè)System.Web.UI.Page對(duì)象。
ASP.NET會(huì)先判斷對(duì)應(yīng)的Page type是否存在于被Cache的Assembly中,如果存在,直接創(chuàng)建Page對(duì)象,否則ASP.NET會(huì)先對(duì)該P(yáng)age的相關(guān)的Source code (包括code behind,html等等) 進(jìn)行編譯,我們也說(shuō)過(guò)這種編譯是一Directory為單位的,也就是說(shuō),處于同一個(gè)Directory下的需要編譯的文件會(huì)被編譯成到同一個(gè)Assembly中。編譯生成的Assembly會(huì)被Cache,用于后續(xù)的Request。
正是因?yàn)閷?duì)資源的首次訪問(wèn)會(huì)導(dǎo)致一次編譯(這樣說(shuō)不太準(zhǔn)確,因?yàn)閯?dòng)態(tài)編譯是以directory為單位進(jìn)行的,應(yīng)該對(duì)對(duì)某個(gè)Directory下的資源進(jìn)行首次訪問(wèn)),這樣會(huì)嚴(yán)重降低Web Application的響應(yīng)速度。所以我們?yōu)榱吮苊膺@種情況,需要預(yù)先對(duì)web site進(jìn)行編譯,所以提高web site的響應(yīng)是進(jìn)行預(yù)編譯的最重要的原因。
同時(shí)動(dòng)態(tài)編譯就以為著Web server上放置的是Source code,而且他們是可被修改的。而對(duì)于一個(gè)開(kāi)發(fā)完畢的Web Application,我們更希望以Binary Assembly的方式進(jìn)行部署,這樣Server上部署的都是Binary Assembly,不怕被別人篡改而導(dǎo)致系統(tǒng)的崩潰,從知識(shí)產(chǎn)權(quán)來(lái)講,也更利于保護(hù)商業(yè)秘密。這也是我們?yōu)槭裁匆M(jìn)行預(yù)編譯的另一個(gè)原因。
下面我們就來(lái)講講如何進(jìn)行預(yù)編譯,以及與編譯背后的原理。同時(shí)在這里我需要特別提出的是,在上一部分講的一些術(shù)語(yǔ)和原理,比如Preservation file,FastObjectFactory,同樣適用于預(yù)編譯,重復(fù)的內(nèi)容,在這里就不必再介紹了。同時(shí)我也將沿用上一部的Sample。如果想看看相關(guān)的內(nèi)容,請(qǐng)參閱[原創(chuàng)]深入剖析ASP.NET的編譯原理之一:動(dòng)態(tài)編譯(Dynamical Compilation)。
2.In Place Pre-compilation V.S. Pre-compilation for Deployment
對(duì)于預(yù)編譯,有可以分為In Place Pre-compilation和Pre-compilation for Deployment,In Place Pre-compilation很簡(jiǎn)單,實(shí)際上就是把整個(gè)Web site編譯到我們一個(gè)臨時(shí)的目錄下面,這個(gè)臨時(shí)目錄也就是我們?cè)诮榻B動(dòng)態(tài)編譯提到的那個(gè)臨時(shí)目錄。而且這個(gè)編譯的方式,包括生成的文件也和動(dòng)態(tài)編譯完全一樣,唯一不同就是編譯的時(shí)間:預(yù)先編譯,編譯的范圍:整個(gè)Web site。這種編譯就是你常用的在VS的build。這種編譯方式一般用于開(kāi)發(fā)階段。
為了部署為目的的編譯是我們今天討論的重點(diǎn),下面我們就著重來(lái)討論P(yáng)re-compilation for Deployment。
注:在ASP.NET的編譯都是通過(guò)一個(gè)叫做aspnet_compiler的工具執(zhí)行的,該工具隨ASP.NET 2.0一起發(fā)布,你完全可以利用此工具以命令行的方式的執(zhí)行編譯,并通過(guò)傳遞不同的命令行開(kāi)關(guān)設(shè)置不同的編譯選項(xiàng)。該工具被置于了VS中,使你可以利用VS進(jìn)行可視化的編譯。
3.Non-updatable Pre-compilation V.S. Updatable Pre-compilation
ASP.NET 2.0為我們提供了幾種不同方式的預(yù)編譯和部署。為了弄清楚這些預(yù)編譯和部署方式,我們先來(lái)回顧一下ASP.NET 1.x下的編譯方式。我們知道在ASP.NET 1.x時(shí)代對(duì)整個(gè)Web site進(jìn)行編譯,實(shí)際上我們只會(huì)對(duì)所有C#和VB.NET等后臺(tái)代碼進(jìn)行編譯,并生成一個(gè)單一的Assembly。而Web page的aspx是不會(huì)參與編譯的。所以當(dāng)我們?cè)L問(wèn)一個(gè)Web page的時(shí)候,ASP.NET必須對(duì)aspx進(jìn)行動(dòng)態(tài)編譯。
這一切之所以能夠進(jìn)行是因?yàn)閃eb page采用的是aspx + code behind的模式。
<%@?Page?Language="C#"?AutoEventWireup="false"?????Codebehind="Default.aspx.cs"
????Inherits="Default"?%>
?
?
從上面我們可以看到aspx和Code behind是一種繼承的關(guān)系,aspx繼承和它對(duì)應(yīng)的Code Behind。ASP.NET可以把Code behind和aspx分開(kāi)進(jìn)行編譯,把它們編譯到不同的Assembly中。我們就是上面的Code為例,
我們現(xiàn)在若對(duì)該Web site進(jìn)行編譯的話,Default.aspx.cs會(huì)被編譯到一個(gè)Assembly中,假設(shè)這個(gè)Assembly為App_Web.dll. 我們把該Dll和aspx部署到Production Server上。如果我們現(xiàn)在訪問(wèn)defaut.aspx。ASP.NET
會(huì)對(duì)aspx進(jìn)行動(dòng)態(tài)編譯,生成的Assembly可以暫時(shí)成為App_Web_aspx.dll。對(duì)于Default.aspx,如果我們?nèi)鏑#代碼來(lái)描述的話,應(yīng)該像下面一樣定義:
?這種編譯方式,我自己把它叫做對(duì)asXx的動(dòng)態(tài)編譯。在ASP.NET2.0 中也沿用了這種編譯方式。這種編譯方式的主要特征是對(duì)Code behind和所有的后臺(tái)代碼進(jìn)行預(yù)編余,aspx(確切地說(shuō)應(yīng)該是asXx:asax,asmx,asax等)原樣部署。由于這種方式的預(yù)編譯,asXx是可以修改的(當(dāng)然這種修改是有一定限制的,因?yàn)閏ode behind已經(jīng)編譯好了,所以這種修改只可能是和code behind無(wú)關(guān)的修改),所以又叫做Updatable Pre-compilation。
除了Updatable Pre-compilation之外,ASP.NET還提供另外一種高效的預(yù)編譯方式,Non-updatable Pre-compilation,之所以叫做不可修改的預(yù)編譯,這是因?yàn)?#xff1a;這種編譯方式把a(bǔ)sXx、Code behind、后臺(tái)代碼甚至是部分Resource都進(jìn)行預(yù)編譯,從而避免了運(yùn)行時(shí)對(duì)asXx的動(dòng)態(tài)編譯,從而最大程度地提高了整個(gè)Web site的響應(yīng)。在部署的時(shí)候,我們除了把生成的Assembly進(jìn)行部署之外,所有的通過(guò)編譯生成的asXx也必須進(jìn)行部署。 不過(guò)需要特別說(shuō)明的是,此時(shí)的asXx文件僅僅是一個(gè)占位的文件而已,它里面不具有任何HTML。
4.Partial class
在ASP.NET 1.x,由于采用的aspx?+ code behind的機(jī)制,對(duì)于任何一個(gè)Web page或者其他ASP.NET 基于axXx的對(duì)象來(lái)說(shuō),都是由兩個(gè)文件、兩個(gè)class組成。兩個(gè)文件是指axXx和code behind,兩個(gè)class是指Code behind定義的繼承自System.Web.UI.Page的class,和一個(gè)繼承自它的由axXx生成的class。
對(duì)于使用過(guò)ASP.NET 1.x來(lái)說(shuō),一定會(huì)很熟悉這樣一種情況:對(duì)于每個(gè)在aspx中通過(guò)HTML定義的Server Control,在Code behind中必須具有一個(gè)對(duì)應(yīng)的protected成員,否則你不能通過(guò)編程的方式訪問(wèn)這個(gè)Server control。以不同方式呈現(xiàn)的同一個(gè)Server control通過(guò)ID關(guān)聯(lián)起來(lái),如果在Code behind中改了Server control的ID,Server control的Server端的Event handler將會(huì)失去原有的作用。
但是在ASP.NET 2.0來(lái)說(shuō),這種情況發(fā)生了改變,在aspx中的Server control在Code behind中卻沒(méi)有相應(yīng)的成員變量,但是我們可以毫無(wú)障礙地訪問(wèn)到每個(gè)Server control。這使得我們的code behind更加簡(jiǎn)潔,通過(guò)避免了Server control在aspx和code bebind中的不匹配的問(wèn)題。這一切都得益于.NET Framework 2.0提供的partial class的機(jī)制:把同一個(gè)class分布于不同文件中進(jìn)行定義。有了這個(gè)概念,我們來(lái)看ASP.NET 2.0的code behind機(jī)制。
比如我們有這樣的一個(gè)Page:
?
Code behind如下:
?
?而實(shí)際上,ASP.NET會(huì)為我們創(chuàng)建一個(gè)隱藏的.cs文件(這個(gè)文件有人 把它稱(chēng)之為Sibling partial class):
?
?這個(gè)文件會(huì)隨著aspx文件的改變而動(dòng)態(tài)變化,所以code behind中的Server control永遠(yuǎn)和aspx中的Server control是完全匹配的。所以我們說(shuō)ASP.NET 2.0的Page是由3個(gè)文件、兩個(gè)class組成的。
5.編譯的粒度和Assembly的命名
到現(xiàn)在為止,我們所講的ASP.NET的預(yù)編譯都是以Directory為單位的,同一個(gè)Directory下的所有需要編譯的文件被編譯到同一個(gè)Assembly中。ASP.NET還支持以Page為單位的預(yù)編譯,也就是每個(gè)Page編譯成一個(gè)Assembly。
在默認(rèn)的情況下,ASP.NET預(yù)編譯生成的Assembly名稱(chēng)是隨機(jī)生成的,也就是每次生成的Assembly都具有不同的name。所以我們?cè)诓渴餡eb site的時(shí)候,一般需要把原來(lái)的Assembly刪掉,再部署新的Assembly。不過(guò)ASP.NET為我們提供了另外一種選擇,使得每次編譯生成的Assembly具有相同的名稱(chēng),這樣我們部署的時(shí)候就可以直接把新的Assembly 拷貝到Production Server上,自動(dòng)覆蓋掉同名的Assembly。
6.??Sample
我們沿用上一部份是用的Sample,我們通過(guò)采用不同的預(yù)編譯方式看看程序?qū)⑷绾芜\(yùn)行。
??? 6.1 Non-updatable Pre-compilation
我們采用如上圖所示的默認(rèn)的發(fā)布方式,ASP.NET 將會(huì)進(jìn)行Non-updatable Pre-compilation。瀏覽目標(biāo)文件夾,我們會(huì)發(fā)現(xiàn) 如下的文件結(jié)構(gòu).
除了多了一個(gè)Bin目錄和PrecompiledApp.config之外,整個(gè)結(jié)構(gòu)和Source code中的結(jié)構(gòu)完全一樣。通過(guò)上面的分析,我們知道這種預(yù)編譯方式是將asXx、code behind、后臺(tái)代碼已經(jīng)Resource一起編譯成Assembly。我們說(shuō)過(guò)對(duì)于這樣的預(yù)編譯方式,aspx僅僅是一個(gè)站位的文件而以,其中HTML已經(jīng)沒(méi)有任何意義了,那么對(duì)于編譯后的aspx中到底是什么東西呢。我們來(lái)一探究竟。打開(kāi)每個(gè)aspx都是一段如下如下一樣文字,并無(wú)任何HTML。
?
This?is?a?marker?file?generated?by?the?precompilation?tool,?and?should?not?be?deleted!?
PrecompiledApp.config里面具有一段簡(jiǎn)短的configuration,表明version和是否可以進(jìn)行進(jìn)一步的修改。
?
<precompiledApp?version="2"?updatable="false"/>?
所有的Assembly被編譯到Bin目錄中,我們來(lái)看看到底生成了一些什么樣的文件在Bin目錄中。
在Bin目錄由兩類(lèi)文件構(gòu)成:Assembly和以complied作為擴(kuò)展名的Preservation file。Preservation file的內(nèi)容和作用在第一部分已經(jīng)詳細(xì)介紹過(guò)了,相信大家不會(huì)感到陌生。Preservation file在這里和動(dòng)態(tài)編譯所起的作用一樣。唯一有一點(diǎn)不同的是,他的結(jié)構(gòu)更加簡(jiǎn)潔,去掉的Dependence file的列表,因?yàn)閷?duì)于Non-updatable Pre-compilation來(lái)說(shuō),每個(gè)Page的以來(lái)的文件都是不可更改的。
<preserve?resultType="6"?virtualPath="/Artech.ASPNETDeployment/App_Code/"?hash="439abe7d"?filehash=""?flags="140000"?assembly="App_Code"?/>
我們來(lái)運(yùn)行以下程序,和動(dòng)態(tài)編譯情況下的輸出結(jié)果比較,看看有什么不同。我們照例先運(yùn)行Default Page。
?
輸出的結(jié)果印證我們前面的討論:處于同一個(gè)目錄下的Default 和Default2被編譯到同一個(gè)Assembly中,關(guān)注于處理邏輯的code?behind的class name為Default和Default2,關(guān)注與可視化界面render的aspx對(duì)應(yīng)的class name被加上的_aspx后綴,如果對(duì)default_aspx和default2_aspx進(jìn)行Reflect的話,你會(huì)發(fā)現(xiàn)他們分別繼承Default和Default2,而后者直接繼承自System.Web.UI.Page。所以default_aspx和default2_aspx是真正的意義上基于Web page的Http handler。像動(dòng)態(tài)編譯一樣,預(yù)編譯生成一個(gè)基于Assembly的FastObjectFactory Type,對(duì)該對(duì)象的描述請(qǐng)參照第一部分。
有了前面的理論基礎(chǔ),相信大家已經(jīng)猜到這時(shí)候,我瀏覽Part I下的Page1和Page2時(shí)的輸出是什么 樣子,由于預(yù)編譯是以目錄為單位的,我們對(duì)Part I下的任何一個(gè)page的訪問(wèn),都會(huì)加載相同的Assembly,所以此時(shí)對(duì)這兩個(gè)Page的訪問(wèn)會(huì)得到一樣的輸出結(jié)果:
?? 6.2 Updatable Pre-compilation
接下來(lái)我們來(lái)對(duì)Web Page進(jìn)行Updatable Pre-compilation,相關(guān)的編譯設(shè)置如下:選擇Allow this precompiled site to be updatable。
生成的文件及其結(jié)構(gòu)和進(jìn)行Non-updatable Pre-compilation,不同的又一下3點(diǎn):
- PrecompiledApp.config:updatable被設(shè)置為true。
<precompiledApp?version="2"?updatable="true"/> - asXx和我們進(jìn)行開(kāi)發(fā)時(shí)內(nèi)容一樣,你如aspx包含的就是HTML,我們可以在部署之后對(duì)他們進(jìn)行和code behind無(wú)關(guān)的修改。
- Preversation file中有加上了Page對(duì)應(yīng)的dependence file列表。
<?xml?version="1.0"?encoding="utf-8"?>
<preserve?resultType="9"?virtualPath="/Artech.ASPNETDeployment/App_GlobalResources/"?hash="439abe7d"?filehash="ff21249472dbf6cb"?flags="140000"?assembly="App_GlobalResources"?resHash="1cba48dd56e28538"?/>
我們來(lái)運(yùn)行一下Web site,看看現(xiàn)在的輸出結(jié)果又有何不同。首先打開(kāi)Default Page:
通過(guò)上圖,我們發(fā)現(xiàn)此時(shí)加載了兩個(gè)相關(guān)的Assembly。我們來(lái)分析一下為什么會(huì)這樣。在分析Updatable Pre-compilation時(shí),我們說(shuō)過(guò):asXx是不會(huì)才與編譯的,只有他們的code behind, 所有的后臺(tái)代碼,資源文件才會(huì)本編譯。對(duì)于一個(gè)page 來(lái)說(shuō),page的code behind被編譯到Assembly中,aspx則不會(huì)。Aspx在運(yùn)行時(shí)實(shí)行動(dòng)態(tài)編譯,所以aspx是可被修改的。在本例中,我們?cè)L問(wèn)Default Page,ASP.NET先對(duì)aspx進(jìn)行編譯,其對(duì)應(yīng)的class name為default_aspx,由于default_aspx繼承與Default,并且Default存在于預(yù)編譯生成的Assembly中,所以這個(gè)Assembly被加載進(jìn)來(lái)。
由于同一個(gè)page最重本編譯到兩個(gè)不同的Assembly中,所以我們此時(shí)訪問(wèn)Part I中的Page1或者Page2,又會(huì)有兩個(gè)Assembly被加載進(jìn)來(lái):
???? 6.3 以Page為單位進(jìn)行預(yù)編譯
前面我們進(jìn)行的都是以directory為單位的預(yù)編譯,現(xiàn)在我們縮小編譯的粒度,以Page為單位進(jìn)行編譯。我們選擇了“Use fixed naming and single page assemblies”選項(xiàng)。那么現(xiàn)在進(jìn)行的是 基于單個(gè)page的non-updatable pre-compilation。通時(shí)由于采用的是fixed naming的編譯方式,每次進(jìn)行編譯生成的Assembly的名稱(chēng)都是一樣的。
現(xiàn)在我們來(lái)看看,編譯之后生成的Assembly:
我們看到編譯器為每個(gè)Page生成了一個(gè)單獨(dú)的Assembly。此時(shí)運(yùn)行程序,你看到的又將不同。如果此時(shí)你訪問(wèn)Default Page,你將看到:
是不是和上面不同,Assembly只有Default對(duì)應(yīng)的兩個(gè)Type,沒(méi)有了處于同一個(gè)目錄下的Default2的Type。因?yàn)镈efault2有它獨(dú)自的Assembly .所以你該會(huì)想到,如果我們現(xiàn)在每訪問(wèn)一個(gè)沒(méi)有被訪問(wèn)過(guò)的Page,就會(huì)有一個(gè)新的Assembly被加載。比如我么訪問(wèn)Part I下的Page1:
???? 6.4編譯強(qiáng)類(lèi)型的Assembly
我們知道可以通過(guò)一個(gè)Public key/Private key pair對(duì)Assembly進(jìn)行簽名,進(jìn)而把它部署到GAC中,我們來(lái)看看如何做。
首先我通過(guò)SN.exe生成Public key/Private key pair并保存到一個(gè)文件中(比如D:\MyKey.keys),然后進(jìn)行如下的編譯設(shè)置
那么我們進(jìn)行編譯就會(huì)生成強(qiáng)類(lèi)型的Assembly。我們可以運(yùn)行我們的程序來(lái)證明:
每個(gè)Assembly有具有一樣的PublicKeyToken,因?yàn)槲覀兪褂玫囊粯拥腜ublic key/Private key pair進(jìn)行對(duì)每個(gè)Assembly簽名的。
?
?
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/InsistYML/archive/2008/07/29/1255625.html
總結(jié)
以上是生活随笔為你收集整理的深入剖析ASP.NET的编译原理之二:预编译(Precompilation)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: VC实现最小化后在系统托盘显示
- 下一篇: .NET网络编程学习(三)