zoukankan      html  css  js  c++  java
  • 深入剖析ASP.NET的编译原理之一:动态编译(Dynamical Compilation)

    Microsoft 的Visual Studio为我们在应用开发中提供的强大功能,我们是有目共睹。借助该工具,是我们的开发 显得更加高效而轻松。从Microsoft把这个IDE的名字从VS.NET 该为VS(比如原来的Visual Studio.NET 2003,现在的版本叫VS2005),可以MS对该IDE的期望和野心:MS要把它改造成一个万能的IDE。不过任何都有其两面性,对于我们广大的开发者来说,VS是我们的各种行为简单化,傻瓜化;但是在另一方面,他也会蒙蔽我们的眼睛,使我们对它背后做的事情视而不见。以我们的ASP.NET Website开发为例,编程、编译、部署都可以借助VS,有了VS一切显得如此简单,每个人都会做,但是我想很多一部分人对一个ASP.NET Website如何进行编译不会很了解。这篇文章就来谈谈背后的故事——ASP.NET是如何进行编译的。由于篇幅的问题整篇文章分两个部分:动态编译Dynamical Compilation和预编译(Precompilation)。

    1.动态编译的过程

    我们先来介绍在动态编译下的大体的执行流程:当ASP.NET收到一个基于某个page的request的时候,先判断该Page和相关的Source code是否编译过,如果没有就将其编译,如果已经编译,就是用已经Load的Assembly直接生成Page对象。

    在这里有下面几点需要注意:

    1). 动态编译是按需编译的,ASP.NET只会编译和当前Request相关的aspx和code。

    2). 动态编译是基于某个目录的,也就是说ASP.NET会把被请求的page所在的目录的所有需要编译的文件进行编译,并生成一个Assembly。

    3). 除了编译生成的Assembly外,动态编译还会生成一系列的辅助文件。

    4). 对相关文件的修改,会导致重新编译,但是修改对当前的Request不起作用。也就是说如果你对某个aspx进行修改,那么对于修改后抵达的Request,会导致重新编译,但是对于之前的Request使用的依然是原来编译好的Assembly。

    5). 编译生成的文件被放在一个临时目录中,这个目录的地址为Windows Directory/Microsoft.NET/Framework/v2.0.50727/Temporary ASP.NET Files。其具体的目录结构如下图所示:

     


    在Temporary ASP.NET Files下的Artech.ASPNETDeployment是IIS中Virtual Directory的名称,以下两级目录的名称由Hash value构成,所以编译生成的文件就保存在c6f16246目录下。这个目录你可以通过HttpRuntime.CodegenDir获得。

    Directory/Microsoft.NET/Framework/v2.0.50727/Temporary ASP.NET Files只是一个默认的临时目录,你可以在web config中的compilation section中设置你需要的临时目录。

    <compilation tempDirectory="d:/MyTempFiles" />

    2.Sample

    现在我用一个Sample来一探ASP.NET是如何进行动态编译的。

     


    在这个Sample中,我建立了一个Website,在根目录下创建了两个Page:Default和Default2。

    在两个子目录Part I和Part II下分别创建了两个Web page:Page1和Page2。在App_Code目录中创建了一个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(
    "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"+ typeInfo.Name + "<br/>");
                }

            }


            
    return refllectionResult.ToString();
        }

    }

    内容很简单,对当前加载的所有相关的Assembly(这些Assembly的Fullname以App_Web打头)进行Reflection,列出所有的Type。这个ReflectAllAssmebly将在5个Web page(Default Page和两队Page1&Page2)的Page_Load事件中被调用。

     

    protected void Page_Load(object sender, EventArgs e)
        
    {
            
    this.Response.Write(Utility.ReflectAllAssmebly());
    }

    Default是列出所有4Page对应的Link以便我们访问它们,在我们再进行编译的情况下在IE中输入对应的URL来访问Default Page。(其他Page的Html中不具有真正的内容,是一个空的page.)

     

    通过上面的显示,我们可以看到现在有一个Assembly:App_Web_wh7-uda5。该Asssembly定一个的Type有5个,  _Default和 default_aspx分别对应Default Page,而Default2和 default2_aspxDefault2 Page的。FastObjectFactory_app_web_wh7_uda5是很重要的Type,我将会在后面对其进行深入介绍。正如我们在上面说过的,动态编译是按需编译,现在我们对Default Page进行访问,由于这次对该Website的第一次访问,所有需要的Source Code,包括aspx,code behind都要进行编译。在这个Sample中,虽然我们并没有访问Default2 page,但是我们说过,动态编译是基于目录的,由于Default Page和Default2 Page都直接置于根目录下,所以ASP.NET会把根目录下的所有文件编译到一个Assembly中。由于Page1和Page2位于子目录Part I和Part II之下,所以不会参与编译。除非我们下载对它进行Request。

    我们现在来访问Part I下的Page1和Page2看看会有什么结果。我们会发现,两次Request获得的输出是一样的:
    通过上面的输出我们发现,当前AppDomain中被加载的Assembly多了一个:App_Web_n1mhegpg。我们可以通过定义在该Assembly中的Type的命名可以猜出该Assembly是对Part I 目录进行编译产生的。Page1和Page2的编译后的Type name变成了part_i_page1_aspx& Page1part_i_page2_aspx& Page2。此外我们看到,该Assembly中依然有一个FastObjectFactory的Type:FastObjectFactory_app_web_n1mhegpg。在这里我需要特别指出的是,名称的后缀都是通过 Hash算法得到的。

    有了上面的理论和实验结果,我想这个时候,你肯定已经想到,如果我现在对Part II的Page1和Page2进行访问,输出结果会是什么样子了。
    如果这个时候,你查看临时目录(Directory/Microsoft.NET/Framework/v2.0.50727/Temporary ASP.NET Files)中该Website对应的子目录,已将会看到生成了一些列的文件。

    3.Page最终被转化成什么?

    我们现在来看看通过编译,一个Page到底转换成什么。我们以Part I/Page1为例。通过上面的Sampe,我们知道它最终变成了两个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中定义了一个基于aspx的HttpHandler所需的所有操作,并且它继承了Page1。所以我们可以说Part I/Page1这个page 最终的体现为part_i_page1_aspx。进一步说,对Part I/Page1的Http Request,ASP.NET所要做的就是生成一个part_i_page1_aspx来Handle这个request就可以了。 

    4.FastObjectFactory

    通过上面的一个简单的Sample,你已经看到每个Assembly中都会生成一个以FastObjectFactory作为前缀的Type。这是一个很重要的Type,从它的名称我们不难猜出它的作用:高效的生成对象。而生成的是什么样的对象呢?实际上就是基于每个aspx的Http request的Http handler,对于Part I/Page1就是我们上面一节分析的part_i_page1_aspx对象。

    我们现在通过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的名称)。通过这些方法,可以快速生成对一个的Page。至于为什么会叫作FastObjectFactory,我想是因为直接通过调用这个静态的方法快速地创建Page对象,从而避免使用Reflection的late binding带来的性能的影响吧。

    5.Preservation Files

    进行每一次编译,ASP.NET会生成一系列具有.compiled扩展名的保留文件(Preservation File)。这个文件非常重要,我们现在来深入介绍这个样一个文件。

    Preservation File这个文件本质上是一个XML。它是基于每个Page的,也就是每个Page都会有一个这样的Preservation File. 无论Page对应的Directory是怎样的,与之对应的Preservation File总会保存在根目录下,所以必须有一种机制保持为处于不同Directory的Page生成的Preservation File具有不同的文件名,不管Page的名称是否一样。所以Preservation File采用下面的命名机制:

    [page].aspx.[folder-hash].compiled

    其中[page]是Page的名称,[folder-hash]是对page所在路径的Hash Value。这样做有两个好处。

    • 保证处于同一级目录的所有Preservation File具有不同的文件名。
    • 保证ASP.NET对于一个Http request可以找到Page对应的Preservation File。

    下面这个Preservation File就是上面Sample中Part II/Page1.aspx对应的Preservation File,名称为default2.aspx.cdcab7d2.compiled。我们来看看XML每个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我们可以看到,所有的内容都包含在preserve XML element中,在他中间定义了几个重要的attribute.

    • virtualPath: Page的虚拟地址。
    • assembly:Assembly名称
    • Type:Page的编译后对应的Type(Http handler)。
    • hash: 一个代表本Preservation File状态的Hash value。
    • filehash: 一个代表本Dependent File状态的Hash value。

    通过hash和filehash的缓存,ASP.NET可以判断自上一次使用以来,Preservation File和它所依赖的Dependent File是否被改动,如果真的被改动,将会重新编译。因为对于文件的任何改动都会导致该Hash value的改变。

    此外,Preservation File的<filedeps>列出了所有依赖的文件,对于Page,一般是aspx和code behind。

    6. 进一步剖析整个动态编译过程

    现在我们来总结整个动态编译的过程:

    Step1:当ASP.NET收到对于某个Page的Request,根据这个request对应的Url试着找到该page对应的Preservation File,如果没有找到,重新编译Page所在目录下的所有需要编译的文件,同时生成一些额外的文件,包括Preservation File。

    Step2:然后解析这个Preservation File,通过hash和filehash判断文件自身或者是Dependent File是否在上一次编译之后进行过任何的修改,如果是的,则重新编译。然后重新执行Step2。

    Step3:通过Preservation File 的assembly attribute Load对应的assembly(如果Assembly还没有被Load的话),通过type获知对应的aspx type,然后借助FastObjectFactory的对应的Create_ASP_XXX创建这个type。这个新创建的对象就是我们需要的Http Handler,在之上执行相应的操作把结果Response到客户端。

    对动态编译的讨论就到这里,在本篇文章下半部分将会讨论另一种更加有用的编译方式: 深入剖析ASP.NET的编译原理之二:预编译(Precompilation)

     

    转自:http://www.cnblogs.com/artech/archive/2007/05/21/753620.html

  • 相关阅读:
    CodeForces 156B Suspects(枚举)
    CodeForces 156A Message(暴力)
    CodeForces 157B Trace
    CodeForces 157A Game Outcome
    HDU 3578 Greedy Tino(双塔DP)
    POJ 2609 Ferry Loading(双塔DP)
    Java 第十一届 蓝桥杯 省模拟赛 19000互质的个数
    Java 第十一届 蓝桥杯 省模拟赛 19000互质的个数
    Java 第十一届 蓝桥杯 省模拟赛 19000互质的个数
    Java 第十一届 蓝桥杯 省模拟赛十六进制转换成十进制
  • 原文地址:https://www.cnblogs.com/jackljf/p/3589119.html
Copyright © 2011-2022 走看看