跳过正文

第2篇:Rails 启动流程分析

·1561 字·8 分钟
xieweicong
作者
xieweicong

学习目标:深入理解 Rails 应用从启动到接收第一个请求的完整过程

引言
#

当你运行 rails server 或者启动一个 Rails 应用时,背后发生了什么?理解启动流程是掌握 Rails 源码的关键,因为它揭示了各个组件是如何组装和初始化的。

1. Rails 应用的生命周期
#

一个 Rails 应用的生命周期大致分为以下阶段:

1. 创建应用 (rails new)
   └─ 生成项目结构和配置文件

2. 启动应用 (rails server)
   ├─ 加载依赖 (Bundler)
   ├─ 初始化 Rails 环境
   ├─ 加载应用配置
   ├─ 运行初始化器
   └─ 启动 Web 服务器

3. 处理请求
   ├─ 路由匹配
   ├─ 执行控制器
   ├─ 渲染视图
   └─ 返回响应

4. 代码重载 (development 环境)
   └─ 监听文件变化,重新加载代码

本章我们重点关注第 1 和第 2 阶段。

2. 阶段一:rails new 创建应用
#

2.1 命令入口
#

当你运行 rails new myapp 时:

$ rails new myapp

这个命令的入口在:

# railties/lib/rails/commands/application/application_command.rb
module Rails
  module Command
    class ApplicationCommand < Base
      def perform(type = nil, *args)
        # 创建新应用
      end
    end
  end
end

2.2 生成的项目结构
#

rails new 会创建以下目录结构:

myapp/
├── app/                  # 应用代码
│   ├── assets/          # 资源文件(CSS, JS, 图片)
│   ├── controllers/     # 控制器
│   ├── models/          # 模型
│   ├── views/           # 视图
│   ├── helpers/         # 辅助方法
│   ├── mailers/         # 邮件类
│   └── jobs/            # 后台任务
├── bin/                  # 可执行脚本
│   ├── rails            # Rails 命令行工具
│   ├── rake             # Rake 任务
│   └── setup            # 环境设置脚本
├── config/               # 配置文件
│   ├── application.rb   # 应用主配置
│   ├── boot.rb          # 启动加载器
│   ├── environment.rb   # 环境加载器
│   ├── routes.rb        # 路由定义
│   ├── database.yml     # 数据库配置
│   ├── environments/    # 环境特定配置
│   │   ├── development.rb
│   │   ├── test.rb
│   │   └── production.rb
│   └── initializers/    # 初始化器
│       └── ...
├── config.ru             # Rack 配置
├── db/                   # 数据库相关
│   ├── migrate/         # 迁移文件
│   └── seeds.rb         # 种子数据
├── Gemfile               # Gem 依赖声明
├── lib/                  # 自定义库
├── log/                  # 日志文件
├── public/               # 静态文件
├── tmp/                  # 临时文件
└── vendor/               # 第三方代码

2.3 关键配置文件详解
#

config/boot.rb
#

这是应用启动的第一个文件:

# config/boot.rb
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)

require 'bundler/setup' # 设置 Bundler,加载 Gemfile 中的 gems

作用

  • 设置 BUNDLE_GEMFILE 环境变量
  • 通过 bundler/setup 加载 Gemfile 中声明的所有 gem
  • 配置 Ruby 的 $LOAD_PATH

config/application.rb
#

应用的主配置文件:

# config/application.rb
require_relative "boot"
require "rails/all"

# 如果你想选择性加载框架:
# require "rails"
# require "active_model/railtie"
# require "active_job/railtie"
# require "active_record/railtie"
# ...

Bundler.require(*Rails.groups)

module MyApp
  class Application < Rails::Application
    # 应用配置
    config.load_defaults 8.0

    # 自定义配置
    # config.time_zone = "Asia/Shanghai"
    # config.eager_load_paths << Rails.root.join("extras")
  end
end

关键点

  1. require "rails/all":加载所有 Rails 组件
  2. Bundler.require(*Rails.groups):根据环境加载 Gemfile 中的 gem
  3. Rails::Application 子类:定义你的应用类
  4. config.load_defaults:加载对应版本的默认配置

config/environment.rb
#

环境加载器,触发应用初始化:

# config/environment.rb
require_relative "application"

# 初始化 Rails 应用
Rails.application.initialize!

config.ru
#

Rack 配置文件,Web 服务器的入口:

# config.ru
require_relative "config/environment"

run Rails.application
Rails.application.load_server

作用

  • 加载 Rails 环境
  • 将 Rails 应用作为 Rack 应用运行

3. 阶段二:rails server 启动应用
#

3.1 启动流程总览
#

rails server
  │
  ├─> 1. 加载 config/boot.rb
  │     └─ 设置 Bundler
  │
  ├─> 2. 加载 config/application.rb
  │     ├─ 加载 Rails 框架
  │     ├─ 定义 Application 类
  │     └─ Bundler.require
  │
  ├─> 3. 加载 config/environment.rb
  │     └─ Rails.application.initialize!
  │         ├─ 运行 initializers(初始化器)
  │         ├─ 加载配置
  │         ├─ 设置中间件栈
  │         └─ 预加载代码(production)
  │
  └─> 4. 启动 Web 服务器(Puma/Webrick)
        └─ 监听端口,等待请求

3.2 详细流程分析
#

让我们逐步分析每个阶段。

步骤 1:执行 rails server 命令
#

入口文件:

# bin/rails
#!/usr/bin/env ruby
APP_PATH = File.expand_path('../config/application', __dir__)
require_relative "../config/boot"
require "rails/commands"

流程

  1. 设置 APP_PATH 常量
  2. 加载 config/boot.rb
  3. 执行 rails/commands

步骤 2:Rails 命令路由
#

# railties/lib/rails/commands.rb
require "rails/command"

aliases = {
  "s"  => "server",
  "c"  => "console",
  "db" => "dbconsole",
  # ...
}

command = ARGV.shift
command = aliases[command] || command

Rails::Command.invoke command, ARGV

作用

  • 解析命令行参数
  • 路由到对应的命令类(Rails::Command::ServerCommand

步骤 3:加载应用
#

# railties/lib/rails/commands/server/server_command.rb
module Rails
  module Command
    class ServerCommand < Base
      def perform
        # 设置环境
        set_application_directory!

        # 加载配置
        require APP_PATH
        Rails.application.require_environment!

        # 启动服务器
        Rails::Server.new(server_options).tap do |server|
          server.start
        end
      end
    end
  end
end

步骤 4:初始化应用
#

当执行 require APP_PATH 时,会加载 config/application.rb,定义应用类:

module MyApp
  class Application < Rails::Application
    # ...
  end
end

然后 Rails.application.require_environment! 会加载 config/environment.rb,触发:

Rails.application.initialize!

3.3 初始化过程(最重要!)
#

initialize! 方法定义在:

# railties/lib/rails/application.rb
class Application < Engine
  def initialize!(group = :default)
    raise "Application has been already initialized." if @initialized
    run_initializers(group, self)
    @initialized = true
    self
  end
end

初始化器系统是 Rails 启动的核心!

4. 初始化器(Initializers)系统
#

4.1 什么是初始化器?
#

初始化器是在应用启动时按特定顺序执行的代码块。Rails 的每个组件(ActiveRecord, ActionPack 等)都通过初始化器来设置自己。

4.2 初始化器的定义
#

在 Railtie 中定义:

# activerecord/lib/active_record/railtie.rb
module ActiveRecord
  class Railtie < Rails::Railtie
    initializer "active_record.initialize_database" do
      # 初始化数据库连接
    end

    initializer "active_record.set_configs" do |app|
      # 设置配置
    end
  end
end

4.3 初始化器的执行顺序
#

初始化器按以下顺序执行:

# 1. Bootstrap 初始化器(最先执行)
Rails::Application::Bootstrap
  ├─ load_environment_config
  ├─ initialize_logger
  └─ initialize_cache

# 2. 各 Railtie 的初始化器(按依赖顺序)
ActiveSupport::Railtie
ActiveRecord::Railtie
ActionController::Railtie
  # ...

# 3. 应用自定义初始化器
# config/initializers/*.rb

# 4. Finisher 初始化器(最后执行)
Rails::Application::Finisher
  ├─ build_middleware_stack
  ├─ eager_load!
  └─ set_routes_reloader

4.4 查看初始化器列表
#

你可以在控制台中查看所有初始化器:

Rails.application.initializers.map(&:name)
# => [
#   "load_environment_config",
#   "active_support.initialize_time_zone",
#   "active_record.initialize_database",
#   "action_controller.set_configs",
#   # ...
# ]

4.5 关键初始化器解析
#

Bootstrap 初始化器
#

# railties/lib/rails/application/bootstrap.rb
module Rails
  class Application
    module Bootstrap
      include Initializable

      initializer :load_environment_config, before: :load_environment_hook do
        # 加载 config/environments/#{Rails.env}.rb
      end

      initializer :initialize_logger, before: :initialize_cache do
        # 设置 Rails.logger
      end

      initializer :initialize_cache, before: :set_eager_load_paths do
        # 设置 Rails.cache
      end
    end
  end
end

ActiveRecord 初始化器
#

# activerecord/lib/active_record/railtie.rb
initializer "active_record.initialize_database" do |app|
  ActiveSupport.on_load(:active_record) do
    # 建立数据库连接
    establish_connection
  end
end

initializer "active_record.set_autoload_paths" do |app|
  # 设置模型自动加载路径
end

ActionController 初始化器
#

# actionpack/lib/action_controller/railtie.rb
initializer "action_controller.set_configs" do |app|
  paths   = app.config.paths
  options = app.config.action_controller

  # 设置控制器配置
  ActiveSupport.on_load(:action_controller) do
    self.logger                     = Rails.logger
    self.cache_store               = Rails.cache
    # ...
  end
end

4.6 添加自定义初始化器
#

你可以在应用中添加自己的初始化器:

# config/application.rb
module MyApp
  class Application < Rails::Application
    # 在特定初始化器之后运行
    initializer "my_app.setup", after: "active_record.initialize_database" do
      # 自定义初始化逻辑
      puts "My custom initializer!"
    end
  end
end

或者在 config/initializers/ 目录下创建文件:

# config/initializers/my_setup.rb
Rails.application.config.to_prepare do
  # 在每次请求前执行(development)
  # 在启动时执行一次(production)
end

5. Railtie - Rails 的插件系统
#

5.1 什么是 Railtie?
#

Railtie 是 Rails 的插件接口,允许组件挂钩到 Rails 的启动过程。

关键概念

  • Railtie:最基础的插件接口
  • Engine:可挂载的小应用(继承自 Railtie)
  • Application:完整的 Rails 应用(继承自 Engine)

继承关系

Rails::Railtie
  ├─ Rails::Engine
  │    └─ Rails::Application
  └─ 其他 Railties(ActiveRecord::Railtie 等)

5.2 Railtie 的作用
#

# railties/lib/rails/railtie.rb
module Rails
  class Railtie
    include Initializable

    class << self
      # 定义初始化器
      def initializer(name, opts = {}, &block)
        # ...
      end

      # 定义配置
      def config
        @config ||= Railtie::Configuration.new
      end

      # 定义 Rake 任务
      def rake_tasks(&block)
        # ...
      end

      # 定义生成器
      def generators(&block)
        # ...
      end
    end
  end
end

5.3 创建自定义 Railtie
#

假设你要创建一个 gem,需要集成到 Rails:

# lib/my_gem/railtie.rb
module MyGem
  class Railtie < Rails::Railtie
    # 添加配置
    config.my_gem = ActiveSupport::OrderedOptions.new
    config.my_gem.option1 = true

    # 添加初始化器
    initializer "my_gem.initialize" do |app|
      MyGem.setup(app.config.my_gem)
    end

    # 添加中间件
    initializer "my_gem.middleware" do |app|
      app.middleware.use MyGem::Middleware
    end

    # 添加 Rake 任务
    rake_tasks do
      load "my_gem/tasks.rake"
    end

    # 添加生成器
    generators do
      require "my_gem/generators"
    end
  end
end

# lib/my_gem.rb
require "my_gem/railtie" if defined?(Rails::Railtie)

6. 自动加载机制(Autoloading)
#

6.1 什么是自动加载?
#

Rails 会自动加载 app/ 目录下的文件,你不需要手动 require

示例

# app/models/user.rb
class User < ApplicationRecord
end

# 在控制器中直接使用,无需 require
class UsersController < ApplicationController
  def index
    @users = User.all  # User 会自动加载
  end
end

6.2 Zeitwerk - Rails 的自动加载器
#

Rails 使用 Zeitwerk 实现自动加载:

# railties/lib/rails/autoloaders.rb
module Rails
  class Autoloaders
    def initialize
      @main = Zeitwerk::Loader.new
      @once = Zeitwerk::Loader.new
    end

    def setup
      @main.push_dir(Rails.root.join("app/models"))
      @main.push_dir(Rails.root.join("app/controllers"))
      # ...
      @main.setup
    end
  end
end

工作原理

  1. 监听常量引用(如 User
  2. 根据命名约定推断文件路径(Userapp/models/user.rb
  3. 加载文件
  4. 验证常量是否定义

6.3 自动加载路径
#

查看自动加载路径:

Rails.application.config.autoload_paths
# => [
#   "/path/to/app/models",
#   "/path/to/app/controllers",
#   "/path/to/app/helpers",
#   # ...
# ]

添加自定义路径:

# config/application.rb
config.autoload_paths << Rails.root.join("lib")

6.4 预加载(Eager Loading)
#

在 production 环境,Rails 会预加载所有代码:

# config/environments/production.rb
config.eager_load = true

原因

  • 避免线程安全问题
  • 启动时发现所有错误
  • 提高性能(不需要按需加载)

7. 启动过程实战追踪
#

让我们实际追踪一次启动过程。

7.1 添加调试输出
#

# config/application.rb
module MyApp
  class Application < Rails::Application
    config.load_defaults 8.0

    # 添加初始化器,打印执行顺序
    initializer "log_initialization", before: :load_environment_config do
      puts "➜ Starting initialization..."
    end

    initializer "log_after_database", after: "active_record.initialize_database" do
      puts "➜ Database initialized"
    end
  end
end

7.2 使用 Benchmark
#

查看启动时间:

# config/environment.rb
require_relative "application"

require 'benchmark'

time = Benchmark.realtime do
  Rails.application.initialize!
end

puts "Application initialized in #{(time * 1000).round(2)}ms"

7.3 使用调试器
#

在关键位置设置断点:

# railties/lib/rails/application.rb
def initialize!(group = :default)
  require 'debug'
  binding.break  # 在这里暂停

  run_initializers(group, self)
  @initialized = true
  self
end

8. 中间件栈(Middleware Stack)
#

8.1 什么是中间件?
#

中间件是处理请求和响应的组件链:

请求 → 中间件1 → 中间件2 → ... → 应用 → ... → 中间件2 → 中间件1 → 响应

8.2 查看中间件栈
#

$ rails middleware

输出:

use Rack::Sendfile
use ActionDispatch::Static
use ActionDispatch::Executor
use ActionDispatch::ServerTiming
use ActiveSupport::Cache::Strategy::LocalCache::Middleware
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use ActionDispatch::RemoteIp
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::ActionableExceptions
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ContentSecurityPolicy::Middleware
use ActionDispatch::PermissionsPolicy::Middleware
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
use Rack::TempfileReaper
run MyApp::Application.routes

8.3 中间件的添加
#

# config/application.rb
config.middleware.use MyMiddleware

# 在特定中间件之前
config.middleware.insert_before ActionDispatch::Static, MyMiddleware

# 在特定中间件之后
config.middleware.insert_after ActionDispatch::Static, MyMiddleware

# 删除中间件
config.middleware.delete Rack::Runtime

9. 不同环境的启动差异
#

9.1 Development 环境
#

# config/environments/development.rb
Rails.application.configure do
  config.cache_classes = false     # 不缓存类
  config.eager_load = false        # 不预加载
  config.consider_all_requests_local = true  # 显示详细错误

  # 启用代码重载
  config.file_watcher = ActiveSupport::EventedFileUpdateChecker
end

特点

  • 代码改动后自动重载
  • 详细的错误信息
  • 启动快,但请求慢(需要按需加载)

9.2 Production 环境
#

# config/environments/production.rb
Rails.application.configure do
  config.cache_classes = true      # 缓存类
  config.eager_load = true         # 预加载所有代码
  config.consider_all_requests_local = false  # 隐藏错误详情

  config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
end

特点

  • 启动慢(预加载所有代码)
  • 请求快(代码已加载)
  • 错误信息简洁

9.3 Test 环境
#

# config/environments/test.rb
Rails.application.configure do
  config.cache_classes = true      # 缓存类(快速测试)
  config.eager_load = false        # 不预加载(只加载需要的)
  config.action_controller.allow_forgery_protection = false
end

10. 启动性能优化
#

10.1 测量启动时间
#

$ time rails runner "puts Rails.env"

10.2 减少 Gem 依赖
#

检查不必要的 gem:

# Gemfile
group :development, :test do
  gem 'debug', platforms: [:mri, :mingw, :x64_mingw]
  # 只在需要时加载
end

10.3 延迟加载
#

# 不要在初始化器中做重量级操作
# 不好的做法:
initializer "heavy_operation" do
  # 执行耗时操作
end

# 好的做法:
config.to_prepare do
  # 在需要时才执行
end

11. 本章总结
#

通过本章学习,你应该理解:

  1. Rails 应用的生命周期:从创建到启动到处理请求
  2. 启动流程:boot → application → environment → initialize
  3. 初始化器系统:Rails 组件如何按顺序初始化
  4. Railtie 插件系统:如何扩展 Rails
  5. 自动加载机制:Zeitwerk 如何工作
  6. 中间件栈:请求如何被处理
  7. 环境差异:development 和 production 的不同

12. 实战练习
#

练习 1:追踪初始化顺序
#

config/application.rb 中添加多个初始化器,使用 beforeafter 控制执行顺序,观察输出。

练习 2:创建自定义 Railtie
#

创建一个 gem,定义 Railtie,添加初始化器和中间件。

练习 3:分析启动时间
#

使用 Benchmark 测量各个初始化阶段的耗时,找出瓶颈。

练习 4:实现简单的自动加载
#

不使用 Rails,用纯 Ruby 实现一个简单的按命名约定加载文件的机制。

13. 下一步
#

在下一篇 ActiveSupport 核心扩展 中,我们将深入探讨:

  • Ruby 核心类的扩展
  • Concern 模块的实现
  • Callbacks 回调系统
  • Autoloading 的底层原理

14. 参考资料
#


上一篇Rails 架构概览

下一篇ActiveSupport 核心扩展

返回学习指南首页