1. 程式人生 > >MVC之前的那點事兒系列(5):Http Pipeline詳細分析(下)

MVC之前的那點事兒系列(5):Http Pipeline詳細分析(下)

文章內容

接上面的章節,我們這篇要講解的是Pipeline是執行的各種事件,我們知道,在自定義的HttpModule的Init方法裡,我們可以新增自己的事件,比如如下程式碼:

public class Test : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.BeginRequest += new EventHandler(context_BeginRequest);
        context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest);
    }

    
void context_AuthenticateRequest(object sender, EventArgs e) { throw new NotImplementedException(); } void context_BeginRequest(object sender, EventArgs e) { throw new NotImplementedException(); } }

然後新增的程式碼,在Pipeline裡執行的時候就會把這些事件給執行了,那麼如何執行並且按照什麼順序執行的呢? 在瞭解這些之前,我們先看看這些事件是如何在HttpApplication裡暴露出來了,新增事件存放在何處的呢?閱讀HttpApplication的原始碼,我們可以看到,所有的事件都是按照如下的形式暴露的,選擇其中兩個看一下:

/// <devdoc><para>[To be supplied.]</para></devdoc> 
public event EventHandler BeginRequest {
    add { AddSyncEventHookup(EventBeginRequest, value, RequestNotification.BeginRequest); }
    remove { RemoveSyncEventHookup(EventBeginRequest, value, RequestNotification.BeginRequest); }
} 

 
/// <devdoc><para>[To be supplied.]</para></devdoc> public event EventHandler AuthenticateRequest { add { AddSyncEventHookup(EventAuthenticateRequest, value, RequestNotification.AuthenticateRequest); } remove { RemoveSyncEventHookup(EventAuthenticateRequest, value, RequestNotification.AuthenticateRequest); } }

可以發現,所有的事件都是呼叫AddSyncEventHookup方法新增進去的,其中第一個引數是以Event+事件名稱的值,這個值是如何得來的,我們找到宣告的程式碼:

private static readonly object EventDisposed = new object();
private static readonly object EventErrorRecorded = new object();
private static readonly object EventPreSendRequestHeaders = new object(); 
private static readonly object EventPreSendRequestContent = new object();
 
private static readonly object EventBeginRequest = new object(); 
private static readonly object EventAuthenticateRequest = new object();
private static readonly object EventDefaultAuthentication = new object(); 
private static readonly object EventPostAuthenticateRequest = new object();
private static readonly object EventAuthorizeRequest = new object();
private static readonly object EventPostAuthorizeRequest = new object();
private static readonly object EventResolveRequestCache = new object(); 
private static readonly object EventPostResolveRequestCache = new object();
private static readonly object EventMapRequestHandler = new object(); 
private static readonly object EventPostMapRequestHandler = new object(); 
private static readonly object EventAcquireRequestState = new object();
private static readonly object EventPostAcquireRequestState = new object(); 
private static readonly object EventPreRequestHandlerExecute = new object();
private static readonly object EventPostRequestHandlerExecute = new object();
private static readonly object EventReleaseRequestState = new object();
private static readonly object EventPostReleaseRequestState = new object(); 
private static readonly object EventUpdateRequestCache = new object();
private static readonly object EventPostUpdateRequestCache = new object(); 
private static readonly object EventLogRequest = new object(); 
private static readonly object EventPostLogRequest = new object();
private static readonly object EventEndRequest = new object(); 

再結合add和remove方法,可以大膽猜想,這些值應該是作為key值用的,我們先看完第2個引數,再來驗證我們的猜想,第2個引數是列舉型別RequestNotification,這裡我們再猜想一下,所有的事件都應該放在統一的地方,然後用這個列舉來區分。讓我們先看看這個列舉類的程式碼:

[Flags]
public enum RequestNotification
{
    BeginRequest = 1,
    AuthenticateRequest = 2,
    AuthorizeRequest = 4,
    ResolveRequestCache = 8,
    MapRequestHandler = 16,
    AcquireRequestState = 32,
    PreExecuteRequestHandler = 64,
    ExecuteRequestHandler = 128,
    ReleaseRequestState = 256,
    UpdateRequestCache = 512,
    LogRequest = 1024,
    EndRequest = 2048,
    SendResponse = 536870912,
}

發現什麼了沒有?雖然使用了Flags標記來記錄以便進行異或查詢,但是這裡的列舉型別好像少了一些吧,仔細對照程式碼發現所有以Post開頭的事件都沒出現在這個列舉類裡,為什麼呢?那這些事件是如何宣告的?回到HttpApplication類來繼續檢視程式碼,

/// <devdoc><para>[To be supplied.]</para></devdoc>
public event EventHandler PostAuthenticateRequest {
    add { AddSyncEventHookup(EventPostAuthenticateRequest, value, RequestNotification.AuthenticateRequest, true); } 
    remove { RemoveSyncEventHookup(EventPostAuthenticateRequest, value, RequestNotification.AuthenticateRequest, true); }
} 

突然發現,這個AddSyncEventHookup方法多了一個引數true,這是幹什麼的呢?我們去檢視這個看看究竟。

internal void AddSyncEventHookup(object key, Delegate handler, RequestNotification notification) { 
    AddSyncEventHookup(key, handler, notification, false); 
}
private void AddSyncEventHookup(object key, Delegate handler, RequestNotification notification, bool isPostNotification) { 
    ThrowIfEventBindingDisallowed(); 

    // add the event to the delegate invocation list 
    // this keeps non-pipeline ASP.NET hosts working
    Events.AddHandler(key, handler);

    // For integrated pipeline mode, add events to the IExecutionStep containers only if 
    // InitSpecial has completed and InitInternal has not completed.
    if (IsContainerInitalizationAllowed) { 
        // lookup the module index and add this notification 
        PipelineModuleStepContainer container = GetModuleContainer(CurrentModuleCollectionKey);
        //WOS 1985878: HttpModule unsubscribing an event handler causes AV in Integrated Mode 
        if (container != null) {
#if DBG
            container.DebugModuleName = CurrentModuleCollectionKey;
#endif 
            SyncEventExecutionStep step = new SyncEventExecutionStep(this, (EventHandler)handler);
            container.AddEvent(notification, isPostNotification, step); 
        } 
    }
} 

原來這個方法有2個重新,第2個多了一個isPostNotification的布林值引數,也就是說通過這個引數節約了很多列舉型別的宣告。

我們來仔細看一下上述的程式碼,在剛開始的時候通過呼叫Events.AddHandler方法,將事件新增到Events集合裡,同時這個key就是我們上面猜想的那些Event+事件名稱,通過註釋我們也可以知道Events是為non-pipeline來準備的,在結合if語句上面的註釋,我們發現在IIS7的整合模式下這些事件是新增到另外一個地方的(通過將事件hanlder包裝成SyncEventExecutionStep型別,然後呼叫container.AddEvent方法將事件新增到另外一個地方),也就是說if上面的Events集合是給IIS6和IIS7經典模式用的,下面的Container裡的資料是給IIS7整合模式用的。

注:經典模式使用了Event+事件名稱做為key值,但整合模式使用了RequestNotification列舉+ isPostNotification布林值集合做為key值,這點區別需要注意一下。

那到底IIS7整合模式下的是存放在何處呢?通過GetModuleContainer方法,最終我們可以查到,這些事件是存放在HttpApplication的ModuleContainers屬性裡,這個屬性的型別是PipelineModuleStepContainer[],個數就是HttpModules的個數,也就是說每個HttpModule在HttpApplication上新增的事件都放在各自的PipelineModuleStepContainer容器裡。

現在我們重新回頭繼續來看上個章節的程式碼:

// Construct the execution steps array 
if (HttpRuntime.UseIntegratedPipeline) { 
    _stepManager = new PipelineStepManager(this);
} 
else {
    _stepManager = new ApplicationStepManager(this);
}
 
_stepManager.BuildSteps(_resumeStepsWaitCallback);

整合模式和經典模式(或IIS6)使用的是不同的StepManager,這個類的BuildSteps方法就是為了建立有序的ExecutionStep,其中包括各種事件的事情以及其它在各時間週期之間穿插的操作,最主要的操作,大家以前就應該知道的,比如哪個週期可以判定使用哪個HttpHandler,以及在哪個週期內執行這個HttpHandler的BeginProcessRequest方法。

由於不同的StepManager處理方式不同,我們先看IIS6以及IIS7經典模式的處理程式碼:

internal override void BuildSteps(WaitCallback stepCallback ) { 
    ArrayList steps = new ArrayList();
    HttpApplication app = _application;

    bool urlMappingsEnabled = false; 
    UrlMappingsSection urlMappings = RuntimeConfig.GetConfig().UrlMappings;
    urlMappingsEnabled = urlMappings.IsEnabled && ( urlMappings.UrlMappings.Count > 0 ); 
 
    steps.Add(new ValidateRequestExecutionStep(app));
    steps.Add(new ValidatePathExecutionStep(app)); 

    if (urlMappingsEnabled)
        steps.Add(new UrlMappingsExecutionStep(app)); // url mappings
 
    app.CreateEventExecutionSteps(HttpApplication.EventBeginRequest, steps);
    app.CreateEventExecutionSteps(HttpApplication.EventAuthenticateRequest, steps); 
    app.CreateEventExecutionSteps(HttpApplication.EventDefaultAuthentication, steps); 
    app.CreateEventExecutionSteps(HttpApplication.EventPostAuthenticateRequest, steps);
    app.CreateEventExecutionSteps(HttpApplication.EventAuthorizeRequest, steps); 
    app.CreateEventExecutionSteps(HttpApplication.EventPostAuthorizeRequest, steps);
    app.CreateEventExecutionSteps(HttpApplication.EventResolveRequestCache, steps);
    app.CreateEventExecutionSteps(HttpApplication.EventPostResolveRequestCache, steps);
    steps.Add(new MapHandlerExecutionStep(app));     // map handler 
    app.CreateEventExecutionSteps(HttpApplication.EventPostMapRequestHandler, steps);
    app.CreateEventExecutionSteps(HttpApplication.EventAcquireRequestState, steps); 
    app.CreateEventExecutionSteps(HttpApplication.EventPostAcquireRequestState, steps); 
    app.CreateEventExecutionSteps(HttpApplication.EventPreRequestHandlerExecute, steps);
    steps.Add(new CallHandlerExecutionStep(app));  // execute handler 
    app.CreateEventExecutionSteps(HttpApplication.EventPostRequestHandlerExecute, steps);
    app.CreateEventExecutionSteps(HttpApplication.EventReleaseRequestState, steps);
    app.CreateEventExecutionSteps(HttpApplication.EventPostReleaseRequestState, steps);
    steps.Add(new CallFilterExecutionStep(app));  // filtering 
    app.CreateEventExecutionSteps(HttpApplication.EventUpdateRequestCache, steps);
    app.CreateEventExecutionSteps(HttpApplication.EventPostUpdateRequestCache, steps); 
    _endRequestStepIndex = steps.Count; 
    app.CreateEventExecutionSteps(HttpApplication.EventEndRequest, steps);
    steps.Add(new NoopExecutionStep()); // the last is always there 

    _execSteps = new IExecutionStep[steps.Count];
    steps.CopyTo(_execSteps);
 
    // callback for async completion when reposting to threadpool thread
    _resumeStepsWaitCallback = stepCallback; 
} 

看著上面的程式碼是不是有似曾相識的感覺,很多講宣告週期的文章都會提到20多個的事件(BeginRequest, EndRequest等),我們來看看這個方法的完整功能都是做了什麼,歸納總結有5點:

  1. 對請求的Request進行驗證,ValidateRequestExecutionStep。
  2. 對請求的路徑進行安全檢查,禁止非法路徑訪問(ValidatePathExecutionStep)。 
  3. 如果設定了UrlMappings, 進行RewritePath(UrlMappingsExecutionStep)。
  4. 執行事件處理函式,比如將BeginRequest、AuthenticateRequest轉化成可執行ExecutionStep在正式呼叫時候執行。
  5. 在這18個事件操作處理期間,根據不同的時機加了4個特殊的ExecutionStep。
    1. MapHandlerExecutionStep:查詢匹配的HttpHandler
    2. CallHandlerExecutionStep:執行HttpHandler的BeginProcessRequest
    3. CallFilterExecutionStep:呼叫Response.FilterOutput方法過濾輸出
    4. NoopExecutionStep:空操作,留著以後擴充套件用

需要注意的是所有的ExecuteionStep都儲存在ApplicationStepManager例項下的私有欄位_execSteps裡,而HttpApplication的BeginProcessRequest方法最終會通過該例項的ResumeSteps方法來執行這些操作(就是我們所說的那些事件以及4個特殊的Steps)。

OK,我們繼續來看IIS7整合模式下是如何處理的,上程式碼:

internal override void BuildSteps(WaitCallback stepCallback) {
    Debug.Trace("PipelineRuntime", "BuildSteps"); 
    //ArrayList steps = new ArrayList();
    HttpApplication app = _application;

    // add special steps that don't currently 
    // correspond to a configured handler
 
    IExecutionStep materializeStep = new MaterializeHandlerExecutionStep(app); 

    // implicit map step 
    app.AddEventMapping(
        HttpApplication.IMPLICIT_HANDLER,
        RequestNotification.MapRequestHandler,
        false, materializeStep); 

    // implicit handler routing step 
    IExecutionStep handlerStep = new CallHandlerExecutionStep(app); 

    app.AddEventMapping( 
        HttpApplication.IMPLICIT_HANDLER,
        RequestNotification.ExecuteRequestHandler,
        false, handlerStep);
 
    // add implicit request filtering step
    IExecutionStep filterStep = new CallFilterExecutionStep(app); 
 
    // normally, this executes during UpdateRequestCache as a high priority module
    app.AddEventMapping( 
        HttpApplication.IMPLICIT_FILTER_MODULE,
        RequestNotification.UpdateRequestCache,
        false, filterStep);
 
    // for error conditions, this executes during LogRequest as a high priority module
    app.AddEventMapping( 
        HttpApplication.IMPLICIT_FILTER_MODULE, 
        RequestNotification.LogRequest,
        false, filterStep); 

    _resumeStepsWaitCallback = stepCallback;
}

以上程式碼有2個地方和IIS6不相同:

  1. IIS7整合模式沒有使用MapHandlerExecutionStep來裝載ExecutionStep(也就是查詢對應的HttpHandler),而是通過MaterializeHandlerExecutionStep類來獲得HttpHandler,方式不一樣,但最終都是呼叫HttpApplication.GetFactory方法來獲取的,只不過IIS7整合模式有一些特殊操作而已罷了。
  2. IIS7整合模式是通過HttpApplication的AddEventMapping方法來新增事件的,從而將事件再次加入到前面所說的ModuleContainers容器。

另外有個很有技巧的程式碼:上述4個Steps所加的週期都不是準確的週期,比如CallHandlerExecutionStep應該是載入RequestNotification的列舉值PreExecuteRequestHandler 和ExecuteRequestHandler之間,為什麼呢?因為本身CallHandlerExecutionStep只是一個特殊的step而不暴露事件的,所以列舉裡也沒有,那怎麼辦?回頭看看AddEventMapping方法的第一個引數,它代表的是HttpModule的名字,檢視其中的程式碼得知,在執行所有事件的時候,會遍歷所有HttpModuel名稱集合然後先執行全部BeginRequest事件,再全部執行AuthenticateRequest事件,以此類推,那我們能不能來偽造一個HttpModule的名稱作為引數傳遞給AddEventMapping方法呢,答案是肯定的,看上面的程式碼,發現有2個偽造的名稱分別是常量字串HttpApplication.IMPLICIT_FILTER_MODULE(值為AspNetFilterModule)和HttpApplication.IMPLICIT_HANDLER(值為ManagedPipelineHandler),而因為之前其它HttpModule裡的各種事件都已經load完了,所以這2個偽造HttpModule的是放在集合的最後面,所以在執行ExecuteRequestHandler類別的事件的時候,最後一個事件肯定就是這個偽造HttpModule的事件,再加上偽造HttpModule裡沒有別的事件,所以它對應的ExecutionStep的執行效果其實和IIS6裡CallHandlerExecutionStep的效果是一樣的,就這樣,通過一個很奇特的技巧達到同樣的目的。

最後,我們來總結一下,在IIS6和IIS7經典模式下,是用 Event+事件名稱做key將所有事件的儲存在HttpApplication的Events屬性物件裡,然後在BuildSteps裡統一按照順序組裝,中間載入4個特殊的ExecutionStep,最後在統一執行;在IIS7整合模式下,是通過HttpModule名稱+RequestNotification列舉值作為key將所有的事件儲存在HttpApplication的ModuleContainers屬性物件裡,然後也在BuildSteps裡通過偽造HttpModule名稱載入那4個特殊的ExecutionStep,最後按照列舉型別的順序,遍歷所有的HttpModule按順序來執行這些事件。讀者可以自行編寫一個自定義的HttpModuel來執行這些事件看看效果如何。

最後關於Pipeline完整的圖如下:

 

 

最後,需要注意的是:HttpApplication不是HttpRuntime所建立,HttpRuntime只是向HttpApplicationFactory提出請求,要求返回一個HttpApplication物件。 HttpApplicationFactory在接收到請求後,會先檢查是否有已經存在並空閒的物件,如果有就取出一個HttpApplication物件返回給HttpRuntime,如果沒有的話,則要建立一個HttpApplication物件給HttpRunTime。

參考資料:

同步與推薦

MVC之前的那點事兒系列文章,包括了原創,翻譯,轉載等各型別的文章,如果對你有用,請推薦支援一把,給大叔寫作的動力。

相關推薦

MVC之前事兒系列5Http Pipeline詳細分析

文章內容 接上面的章節,我們這篇要講解的是Pipeline是執行的各種事件,我們知道,在自定義的HttpModule的Init方法裡,我們可以新增自己的事件,比如如下程式碼: public class Test : IHttpModule { public void Init(HttpAp

MVC之前事兒系列4Http Pipeline詳細分析

文章內容 繼續上一章節的內容,通過HttpApplicationFactory的GetApplicationInstance靜態方法獲取例項,然後執行該例項的BeginProcessRequest方法進行執行餘下的Http Pipeline 操作,程式碼如下: // Get application i

MVC之前事兒系列8UrlRouting的理解

文章內容 根據對Http Runtime和Http Pipeline的分析,我們知道一個ASP.NET應用程式可以有多個HttpModuel,但是隻能有一個HttpHandler,並且通過這個HttpHandler的BeginProcessRequest(或ProcessRequest)來處理並返回請求,前

MVC之前事兒系列1:進入CLR

MVC之前的那點事兒系列,是筆者在2012年初閱讀MVC3原始碼的時候整理的,主要講述的是從HTTP請求道進入MVCHandler之前的內容,包括了原創,翻譯,轉載,整理等各型別文章,當然也參考了部落格園多位大牛的文章,對此表示感謝,這次有時間貼出來,希望對大家有用。 主要內容 本文講解的是:伺服器接受H

MVC之前事兒系列7WebActivator的實現原理詳解

文章內容 上篇文章,我們分析如何動態註冊HttpModule的實現,本篇我們來分析一下通過上篇程式碼原理實現的WebActivator類庫,WebActivator提供了3種功能,允許我們分別在HttpApplication初始化之前,之後以及ShutDown的時候分別執行指定的程式碼,示例如下: [

MVC之前事兒系列9MVC如何在Pipeline中接管請求的?

文章內容 上個章節我們講到了,可以在HttpModules初始化之前動態新增Route的方式來自定義自己的HttpHandler,最終接管請求的,那MVC是這麼實現的麼?本章節我們就來分析一下相關的MVC原始碼來驗證一下我們的這個問題。 先建立一個MVC3的Web Application,選擇預設的模

MVC之前事兒系列3HttpRuntime詳解分析

文章內容 話說,經過各種各樣複雜的我們不知道的內部處理,非託管程式碼正式開始呼叫ISPAIRuntime的ProcessRequest方法了(ISPAIRuntime繼承了IISPAIRuntime介面,該介面可以和COM進行互動,並且暴露了ProcessRequest介面方法)。至於為什麼要呼叫這個方法,

MVC之前事兒系列2HttpRuntime詳解分析

文章內容 從上章文章都知道,asp.net是執行在HttpRuntime裡的,但是從CLR如何進入HttpRuntime的,可能大家都不太清晰。本章節就是通過深入分析.Net4的原始碼來展示其中的重要步驟。請先看下圖:   首先,CLR在初始化載入的時候,會載入一個非常重要的類AppManagerApp

MVC之前事兒系列6動態註冊HttpModule

文章內容 通過前面的章節,我們知道HttpApplication在初始化的時候會初始化所有配置檔案裡註冊的HttpModules,那麼有一個疑問,能否初始化之前動態載入HttpModule,而不是隻從Web.config裡讀取? 答案是肯定的, ASP.NET MVC3釋出的時候提供了一個Microsof

MVC之前事兒系列10MVC為什麼不再需要註冊萬用字元*.*了?

文章內容 很多教程裡都提到了,在部署MVC程式的時候要配置萬用字元對映(或者是*.mvc)到aspnet_ISPAI.dll上,在.NET4.0之前確實應該這麼多,但是.NET4.0之後已經不要再費事了,因為它預設就支援了。 你可以會問,沒有對映配置,請求這麼可能會走到aspnet_ISPAI.dll

MVC之前事兒 ---- 系列文章

需要 cnblogs post omx pip 實現原理 內容 activator div MVC之前的那點事兒系列,是筆者在2012年初閱讀MVC3源碼的時候整理的,主要講述的是從HTTP請求道進入MVCHandler之前的內容,包括了原創,翻譯,轉載,整理等各類型文

算法筆記復雜度分析

n+1 增長 角度 復雜 判斷 and 就是 ret 執行時間 (一)漸進符號(這裏暫時只考慮大O) 以輸入規模n為自變量建立的時間復雜度實際上還是較復雜的,例如an2+bn+c+1,不僅與輸入規模有關,還與系統a、b和c有關。此時對該函數進一步抽象,僅考慮運行時間的

EventBus原始碼分析執行緒模型分析2.4版本

EventBus有四種執行緒模型 PostThread模式不需執行緒切換,直接在釋出者執行緒進行事件處理。 MainThread模式分類討論:釋出者執行緒是主執行緒則直接呼叫事件處理方法,否則通過Handler進行執行緒切換,切換到主執行緒處理事件,該模

Android單元測試的利器JuintJuint的詳細用法

前言 這幾天正在成都出差,欣賞著成都的妹紙。 當我開始寫這篇的時候是上週五,沒想到這麼快就星期二了,東西越寫越多,為了保持文章儘量短小精悍,Juint的詳細用法就分成多篇來寫把,具體能寫幾篇我也不清楚… 正文 Assertions(斷言) 斷言

三十歲以前不要在乎的事情- -轉自http://smallcar.bokee.com/3123902.html

三十歲以前不要在乎的事情- -                                             1.放棄 :把握的反面就是放棄,選擇了一個機會,就等於放棄了其它所有的可能。當新的機會擺在面前的時候,敢於放棄已經獲得的一切,這不是功虧一簣,這不是半途而廢

聽課筆記第五講 學習的可行性分析一些概念和思想 (臺灣國立大學機器學習基石

Training versus Testing1,回顧:學習的可行性?最重要的是公式: (1) 假設空間H有限(M),且訓練資料足夠大,則可以保證測試錯誤率Eout 約等於訓練錯誤率Ein;(2)如果能得到Ein 接近於零,根據(1),Eout 趨向於零。以上兩條保證的學習的可能性。可知,假設空間H 的

談談源碼管理事兒——源碼管理十誡

我不 evel .html 文件夾 jetbrains enable thum XML 構建 引言: 若是還有能夠毫無偏見地涉及各個編程語言。比源碼管理軟件更必要的工具。我倒是非常想見識一下。源碼管理軟件是我們工作的必備工具,是很多開發團隊的血液。那為什麽我們都

Maven 事兒

做到 conn active cep ant tab name www color 0. 前言 Jason Van Zyl,在 Java 十大風雲人物排行榜上或許會看到他。 這兄弟是幹嘛的? 他就是 Maven 的創始人,人們都尊稱他為“Maven 他爸&

Map 大家族的事兒 ( 5 ) WeakHashMap

WeakHashMap是一個基於Map介面實現的散列表,實現細節與HashMap類似(都有負載因子、雜湊函式等等,但沒有HashMap那麼多優化手段),它的特殊之處在於每個key都是一個弱引用。 首先我們要明白什麼是弱引用,Java將引用分為四類(從JDK1.2開始),強度

資料分析事兒

在之前我們給大家講了講什麼是資料分析以及資料分析的目的,資料分析就是通過使用合適的方法進行統計,統計也不是隨隨便便的統計的,需要找對方法。統計分析方法對收集來的大量資料進行分析,提取有用資訊和形成結論而對資料加以詳細研究和概括總結的過程。而資料分析的目的就是通過分析資料找到企業未來的發展情況。今天就給大家