学习目标:深入理解 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
关键点:
require "rails/all"
:加载所有 Rails 组件Bundler.require(*Rails.groups)
:根据环境加载 Gemfile 中的 gemRails::Application
子类:定义你的应用类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"
流程:
- 设置
APP_PATH
常量 - 加载
config/boot.rb
- 执行
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
工作原理:
- 监听常量引用(如
User
) - 根据命名约定推断文件路径(
User
→app/models/user.rb
) - 加载文件
- 验证常量是否定义
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. 本章总结#
通过本章学习,你应该理解:
- Rails 应用的生命周期:从创建到启动到处理请求
- 启动流程:boot → application → environment → initialize
- 初始化器系统:Rails 组件如何按顺序初始化
- Railtie 插件系统:如何扩展 Rails
- 自动加载机制:Zeitwerk 如何工作
- 中间件栈:请求如何被处理
- 环境差异:development 和 production 的不同
12. 实战练习#
练习 1:追踪初始化顺序#
在 config/application.rb
中添加多个初始化器,使用 before
和 after
控制执行顺序,观察输出。
练习 2:创建自定义 Railtie#
创建一个 gem,定义 Railtie,添加初始化器和中间件。
练习 3:分析启动时间#
使用 Benchmark
测量各个初始化阶段的耗时,找出瓶颈。
练习 4:实现简单的自动加载#
不使用 Rails,用纯 Ruby 实现一个简单的按命名约定加载文件的机制。
13. 下一步#
在下一篇 ActiveSupport 核心扩展 中,我们将深入探讨:
- Ruby 核心类的扩展
- Concern 模块的实现
- Callbacks 回调系统
- Autoloading 的底层原理
14. 参考资料#
- Rails 启动过程官方文档
- Zeitwerk 文档
- Rails 源码位置:
上一篇:Rails 架构概览
返回:学习指南首页