Rails Rails 是如何做到 development 下修改代码后,不重启服务器就可以重新加载代码?

zhangyuan · 2013年10月24日 · 最后由 mijael 回复于 2016年11月13日 · 6812 次阅读

在 development 环境下,修改项目代码,不用重启服务器,刷新页面就能看到代码变化。为什么?是用 load 重新加载了代码吗?——没那么简单。

下面在 Rails-3.2.13 代码里找到它的实现。

首先,这和 Rails 加载常量的方式有关。 ActiveSupport::Dependencies::ModuleConstMissing 模块重写了 Module#const_missing 方法。当程序遇到未定义的常量时,就会调用到该方法。const_missing 是类方法,所以应该定义在类的类 Class 上,而 ModuleClass 的父类。在重写的 const_missing 方法里,会根据常量名,按照路径的惯例去加载对应的文件,当找到后,就加载文件,并且把常量保存起来(常量保存在 ActiveSupport::Dependencies.autoloaded_constants ,加载过的文件路径保存在 ActiveSupport::Dependencies.history )。当需要时,调用 ActiveSupport::Dependencies.clear 移除已经加载过的常量(可参考 http://blog.yuaz.net/archives/415 ,很多人认为 Rails 加载类的方式,用到了 autoload http://ruby-doc.org/core-1.9.3/Module.html#method-i-autoload ,是不对的)。

所以,Rails 可以用 ActiveSupport::Dependencies.clear 在某个时刻清除加载的类和模块的。

在代码里搜 ActiveSupport::Dependencies.clear ,找到 railties/lib/rails/application/finisher.rb 里可以看到,有如下的初始化步骤。这里定义了一个 lambda

# Set app reload just after the finisher hook to ensure
# paths added in the hook are still loaded.
initializer :set_clear_dependencies_hook, :group => :all do
  callback = lambda do
    ActiveSupport::DescendantsTracker.clear
    ActiveSupport::Dependencies.clear
  end

  if config.reload_classes_only_on_change
    reloader = config.file_watcher.new(*watchable_args, &callback)
    self.reloaders << reloader
    # We need to set a to_prepare callback regardless of the reloader result, i.e.
    # models should be reloaded if any of the reloaders (i18n, routes) were updated.
    ActionDispatch::Reloader.to_prepare(:prepend => true){ reloader.execute }
  else
    ActionDispatch::Reloader.to_cleanup(&callback)
  end
end

然后将其传给 ActionDispatch::Reloader.to_prepareActionDispatch::Reloader.to_cleanup 。打开其源码 actionpack/lib/action_dispatch/middleware/reloader.rb 看到 ActionDispatch::Reloader 是一个 rack middleware,在其 ActionDispatch::Reloader#call 方法里,会在每次请求前( ActionDispatch::Reloader#prepare! )和请求后( ActionDispatch::Reloader#cleanup! ),调用到初始化步骤里的 callback,此时就移除了已经加载过的常量。这样每次请求前或者后,就会移除以及加载的常量;对代码的改动,就会立即生效。

那么,为什么在 development 环境下,会重新加载代码,在 production 下不会呢?

搜索 ActionDispatch::Reloader 关键字,可以在 railties/lib/rails/application.rb 找到

unless config.cache_classes
  app = self
  middleware.use ::ActionDispatch::Reloader, lambda { app.reload_dependencies? }
end

在此处看到,有个开关 config.cache_classes 。在 Rails 生成的环境配置里, config/environments/development.rb里有 config.cache_classes = falseconfig/environments/production.rb 里有 config.cache_classes = true 。所以,只有在 development 环境下才会使用 ::ActionDispatch::Reloader。因此,production 下修改代码,不会生效,必须重启。

最终的结论就是:在 development 下 Rails 使用了 ActionDispatch::Reloader 这个 middleware,在每次请求时调用 ActiveSupport::Dependencies.clear 来移除已经加载的类和模块;这样在程序用到他们时,就会重新加载。

另外,在 production 环境下,Rails 并不是在用到这些常量时,才去通过 const_missing 的方式加载。而是在初始化的时候,用 eager_load! 的方式,把 Rails.configuration.eager_load_paths 里的文件都加载(默认是 app 的若干目录,可见 railties/lib/rails/engine/configuration.rb 里的 Rails::Engine::Configuration#paths,这样也是为了线程安全吧)。

来源 http://blog.yuaz.net/archives/462

貌似还没认真研究过 rails,惭愧

赞,写的很详细

学习啊,赞,写得非常好

development 下,是不是所有 app/* 下的文件都是可以自动 reload 的

#6 楼 @hiveer 你可以打印一下 ActiveSupport::Dependencies.autoload_paths 的结果。返回值里的目录,是自动 reload 的

第一条就是我想要加载的文件

#9 楼 @hiveer 其他的都是目录,你这个第一条怎么是文件?

你可以在 config/application.rb 里添加

config.autoload_paths += %W(#{config.root}/app/models/modules)

http://stackoverflow.com/questions/17729345/when-do-i-need-to-restart-server-in-rails

结论就是在 development 模式下除了修改 app 目录下和 config/routes.rb 之外,都需要重启服务器

时隔两年,重新看了一遍,收获很大。

学习了

需要 登录 后方可回复, 如果你还没有账号请 注册新账号