Ruby On Rails-2.0.2源代碼分析(1)-Rails的啓動

  • 前言

  本文主要是針對Ruby On Rails 2.0.2的源代碼進行分析,學習與研究。所使用的工具是NetBean 6.1 Beta,WEBRick,SciTE,ruby-debug-base(0.10.0),ruby-debug-ide(0.1.10)。Ruby版本爲1.8.6。
  應該怎麼分析總結,是開始最令人頭痛的事,Ruby是面向對象的語言,從對象的層次記錄吧,似乎一切都不那麼直觀,一個龐大的系統擺在眼前,整理一個類圖,繼承關係圖。。。有點牛啃南瓜,無從下口的感覺。最後,決定打算從Ruby的本質-解釋語言下手,從解釋器的角度出發,跟着解釋器的步伐,從細微入手,一步一步深入Rails,以達到從局部到整體,瞭解學習的目的。所以,最終,我決定從源代碼執行順序的角度去分析Rails。
  爲方便起見,我直接使用NetBean的調試環境,使用Ruby自帶的WEBRick,從接觸Rails最基本的ruby script/server開始,首先來看Rails是怎麼啓動起來的。

  • Rails的啓動

  ruby script/server,應該是搞rails的同學們耳熟能詳的命令了。server腳本主要執行兩個的過程:1.啓動Rails;2.啓動web服務器(當然,我這裏是啓動WEBRick了)。我們就從這裏入手,看看Rails是怎麼樣被啓動起來的。
  前面我說過,我將從源代碼執行順序的角度去分析,所以,讓我們先來看一看Rails啓動時,核心源代碼的執行順序,具體見下圖(爲了使得分析簡單明瞭,抓住關鍵本質所在,我只把個人認爲與啓動有關的源代碼列出來,執行過程中,其他類似關於ActiveSupport中關於core的extension之類的代碼就不列出):

  boot.rb

  源代碼路徑:RAILS_ROOT/config/boot.rb
  這個代碼文件是Rails的啓動入口,完成的功能是:首先判斷Rails是否啓動,如果未啓動則先執行一個「預初始化」(preinitialize)過程,然後選擇一種啓動方式(Vendor/Gem),執行相應類上的run方法。主方法boot!代碼如下:

Ruby代碼
  1. def boot!  
  2.   unless booted?  
  3.     preinitialize  
  4.     pick_boot.run  
  5.   end  
  6. end  
def boot!
  unless booted?
    preinitialize
    pick_boot.run
  end
end

  其中,與初始化過程是執行RAILS_ROOT/config目錄下面的preinitializer.rb(如果存在的話)。這個過程的目的是在加載environment.rb文件執行執行一些初始化工作。參見:http://yudionrails.com/2008/1/7/what-s-new-in-edge-rails-pre-environment-load-hook 。此源代碼中包含一個module Rails,此模塊下面包括三個類:VendorBoot,GemBoot,他們都繼承自Boot類,分別代表是通過Vendor還是Gem的方式啓動 Rails(如果RAILS_ROOT/vender/下面存在名爲rails的目錄,則以Vendor方式啓動Rails,否則,從Gem啓動 Rails)。當使用Gem方式啓動Rails的話,還有一個重要的功能就是判斷加載哪個版本的Rails,當然,正如我們所知,environment.rb中的RAILS_GEM_VERSION起了作用。總得來說,boot代碼邏輯較簡單,沒有什麼費解的東西,下面給出這個文件的整個執行邏輯流程圖:


initialize.rb

  源代碼路徑:gems/rails-2.0.2/lib/initializer.rb (Gem方式啓動)
                    RAILS_ROOT/vendor/rails/railties/lib/initializer.rb(Vendor方式啓動)

  雖然兩種不同啓動方式執行的源代碼不同,但是他們完成的功能都大同小異,都對Rails執行必要的配置以及初始化。我們先來看看上一步--執行boot.rb代碼的最後一步(還記得pick_boot.run麼?),具體代碼如下:

 

Ruby代碼
  1. class Boot  
  2.   def run  
  3.     load_initializer  
  4.     Rails::Initializer.run(:set_load_path)  
  5.   end  
  6. end  
class Boot
  def run
    load_initializer
    Rails::Initializer.run(:set_load_path)
  end
end

當完成了選擇一個boot方式後,會執行相應Boot對象的run方法,那麼run方法首先載入初始化器,VendorRoot通過如下方式載入:

Ruby代碼
  1. class VendorBoot < Boot  
  2.   def load_initializer  
  3.     require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"  
  4.   end  
  5. end  
class VendorBoot < Boot
  def load_initializer
    require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
  end
end

GemRoot通過如下方式載入:

Ruby代碼
  1. class GemBoot < Boot  
  2.   def load_initializer  
  3.     self.class.load_rubygems  
  4.     load_rails_gem  
  5.     require 'initializer'  
  6.   end  
  7.   ...  
  8. end  
class GemBoot < Boot
  def load_initializer
    self.class.load_rubygems
    load_rails_gem
    require 'initializer'
  end
  ...
end

OK。初始化器載入完成,Boot的run方法立即執行初始化器對象的類方法run,注意這裏的run方法參數是:set_load_path符號。好了,下面,我們可以深入到initialize.rb裏面去看個究竟了。  initialize.rb中定義了一個module Rails,其中包括了此代碼文件中最重要的兩個類:Configuration和Initializer。從類名我們就可以很清晰的瞭解到,Initializer類完成Rails的初始化工作,當然這個過程需要各種各樣的配置,參數,這些則由Configuration提供。那麼首先來看看Configuration提供了Rails所需的哪些配置參數,詳見下表:

配置名(accessor名) 具體描述
frameworks 會被載入的Rails框架組件列表,會包括action_controller,action_view等
load_paths 附加的load路徑列表,app/controller;app/models等Rails項目下的目錄
load_once_paths Rails只會load一次的目錄,似乎目前版本的Rails未用到這個參數
log_path 日誌文件的路徑,根據目前的環境(development,test,production)決定
log_level Rails日誌器的日誌級別(info,debug)
view_path view的目錄路徑,默認路徑是app/view了
controller_paths controller的目錄路徑,默認路徑是app/controller
cache_classes 是否對類進行緩存。目前未使用(一直是false)
whiny_nils true/false,當設置爲true的話,當你在Rails中調用一個nil方法的時候,將會得到警告
plugins 載入的插件列表,默認爲空
plugin_paths 插件路徑,默認是RAILS_ROOT/vendor/plugins目錄
plugin_locators 插件的定位器,默認是Plugin::FileSystemLocator
plugin_loader 插件的載入器,默認是Plugin::Loader
database_configuration_file 數據庫配置文件,默認位於RAILS_ROOT/config/database.yml

  繞了一圈,現在讓我們回到Initializer類的run方法(由boot.rb調用:Rails::Initializer.run(:set_load_path)),十分簡單:

Ruby代碼
  1. def self.run(command = :process, configuration = Configuration.new)  
  2.   yield configuration if block_given?  
  3.   initializer = new configuration  
  4.   initializer.send(command)  
  5.   initializer  
  6. end  
def self.run(command = :process, configuration = Configuration.new)
  yield configuration if block_given?
  initializer = new configuration
  initializer.send(command)
  initializer
end

現在我們姑且不管block(boot.rb調用他的時候確實也沒有關聯一個block),接下來的工作是生成一個新的Configuration對象,並賦給Initializer的構造函數(然後由Initializer對象保存該配置對象),然後執行initializer上的command方法,默認情況是執行process方法,這裏通過boot.rb的調用,將執行set_load_path方法。在這裏值得注意的是,新生成的 Configuration對象的所有配置參數都是默認值,例如:frameworks參數通過如下方法得到默認值:

Ruby代碼
  1. def default_frameworks  
  2.   [ :active_record:action_controller:action_view:action_mailer:active_resource ]  
  3. end  
def default_frameworks
  [ :active_record, :action_controller, :action_view, :action_mailer, :active_resource ]
end

controller_path參數通過如下方法得到默認值:

 

Ruby代碼
  1. def default_controller_paths  
  2.   paths = [File.join(root_path, 'app''controllers')]  
  3.   paths.concat builtin_directories  
  4.   paths  
  5. end  
def default_controller_paths
  paths = [File.join(root_path, 'app', 'controllers')]
  paths.concat builtin_directories
  paths
end

其實,所有的這些配置都不是定死的,我們可以在enviroment.rb中重新定義他們,象下面這樣:

Ruby代碼
  1. Rails::Initializer.run do |config|  
  2.   config.frameworks -= [ :active_record:active_resource:action_mailer ]  
  3.   config.plugins = [ :exception_notification:ssl_requirement:all ]  
  4.   ...  
  5. end  
Rails::Initializer.run do |config|
  config.frameworks -= [ :active_record, :active_resource, :action_mailer ]
  config.plugins = [ :exception_notification, :ssl_requirement, :all ]
  ...
end

到這裏,initializer.rb的介紹暫時結束,只是簡單的執行了set_load_path方法設置load路徑。接下來,執行流程回到了script/server:

Ruby代碼
  1. require File.dirname(__FILE__) + '/../config/boot'  
  2. require 'commands/server'  
require File.dirname(__FILE__) + '/../config/boot'
require 'commands/server'

該執行第二句了,下面輪到server.rb出場了。

  server.rb

源代碼路徑:gems/rails-2.0.2/lib/commands/server.rb
  server.rb主要完成兩個功能:1.加載active_support;2.加載web服務器。
  加載active_support十分簡單,只是通過源代碼開始的第一句:

Ruby代碼
  1. require 'active_support'  
require 'active_support'

加載web服務器相比複雜一些。首先,Rails會試圖加載FastCGI,然後會試圖加載mongrel,如下代碼所示:

Ruby代碼
  1. begin  
  2.   require_library_or_gem 'fcgi'  
  3. rescue Exception  
  4.   # FCGI not available  
  5. end  
  6.   
  7. begin  
  8.   require_library_or_gem 'mongrel'  
  9. rescue Exception  
  10.   # Mongrel not available  
  11. end  
begin
  require_library_or_gem 'fcgi'
rescue Exception
  # FCGI not available
end

begin
  require_library_or_gem 'mongrel'
rescue Exception
  # Mongrel not available
end

最終,會通過defined?(Mongrel)和defined?(FCGI)來決定使用哪種服務器。當然,本文前面提到了將使用WEBRick作爲web服務器,這裏最終加載的服務器當然是webrick。server.rb的最後一句代碼: 

Ruby代碼
  1. require "commands/servers/#{server}"   
require "commands/servers/#{server}"

在這裏#{server}當然是webrick了,所以,接下來執行的將是webrick.rb

  webrick.rb

源代碼路徑:gems/rails-2.0.2/lib/commands/servers/webrick.rb
  webrick.rb完成如下幾個主要功能:1.加載Ruby自帶的webrick庫;2.加載environment.rb;3.加載 webrick_server.rb;4.執行DispatchServlet(在webrick_server.rb中定義)的類方法 dispatch。整個過程都是順序完成的,所以,示意的源代碼可以如下所示:

Ruby代碼
  1. require 'webrick'  
  2. require RAILS_ROOT + "/config/environment"  
  3. require 'webrick_server'  
  4. DispatchServlet.dispatch(OPTIONS)  
require 'webrick'
  require RAILS_ROOT + "/config/environment"
  require 'webrick_server'
  DispatchServlet.dispatch(OPTIONS)

  environment.rb

  源代碼路徑:RAILS_ROOT/config/environment.rb
  回到了我們熟悉的environment.rb中,在這裏我們可以對Rails運行的環境進行配置,這裏不做過多闡述,可以參與相關Rails文檔。

  webrick_server.rb

源代碼路徑:gems/rails-2.0.2/lib/webrick_server.rb
  這是Rails啓動所執行的最後一個源代碼文件。我前面提到了一點,此源代碼文件中定義了DispatchServlet,這是一個自定義的dispatch servlet,用於將瀏覽器的請求dispatch到相應的controller,action上。因爲這裏我只打算介紹Rails的啓動,所以,我們只用關注如下的代碼即可:

Ruby代碼
  1. class DispatchServlet < WEBrick::HTTPServlet::AbstractServlet  
  2.   
  3.   # Start the WEBrick server with the given options, mounting the  
  4.   # DispatchServlet at <tt>/</tt>.  
  5.   def self.dispatch(options = {})  
  6.     Socket.do_not_reverse_lookup = true # patch for OS X  
  7.   
  8.     params = { :Port        => options[:port].to_i,  
  9.                :ServerType  => options[:server_type],  
  10.                :BindAddress => options[:ip] }  
  11.     params[:MimeTypes] = options[:mime_typesif options[:mime_types]  
  12.   
  13.     server = WEBrick::HTTPServer.new(params)  
  14.     server.mount('/', DispatchServlet, options)  
  15.   
  16.    trap("INT") { server.shutdown }  
  17.     server.start  
  18.   end  
  19. end  
class DispatchServlet < WEBrick::HTTPServlet::AbstractServlet

  # Start the WEBrick server with the given options, mounting the
  # DispatchServlet at <tt>/</tt>.
  def self.dispatch(options = {})
    Socket.do_not_reverse_lookup = true # patch for OS X

    params = { :Port        => options[:port].to_i,
               :ServerType  => options[:server_type],
               :BindAddress => options[:ip] }
    params[:MimeTypes] = options[:mime_types] if options[:mime_types]

    server = WEBrick::HTTPServer.new(params)
    server.mount('/', DispatchServlet, options)

   trap("INT") { server.shutdown }
    server.start
  end
end

這是一個經典的啓動WEBRick服務器的方式,不用過多闡述,可以參考webrick的相關文檔。當然,webrick_server.rb文件中還有一個相當重要的類CGI,並且,DispatchServlet類中還有一些重要的方法,我們暫時可以將他們擱在一旁。以後進一步分析Rails的時候再講解。  OK。至此,Rails就正式上馬了,web服務器也啓動起來了,接下來的事情,當然是等着web服務器將request轉發給Rails進行處理了,按照國際慣例,這當然是下文分解的事了~

 

thanks:http://woody-420420.iteye.com/blog/170683