查看源代码 Application 行为 (Elixir v1.16.2)
用于处理应用程序和定义应用程序回调的模块。
应用程序是 Erlang/OTP 中打包软件的惯用方式。简单来说,它们类似于其他编程语言中常见的“库”概念,但具有一些额外的特征。
应用程序是实现某些特定功能的组件,具有标准化的目录结构、配置和生命周期。应用程序可以加载、启动和停止。每个应用程序也有自己的环境,它提供了一个统一的 API 用于配置每个应用程序。
开发人员通常与应用程序环境及其回调模块交互。因此,我们将首先介绍这些主题,然后再深入了解应用程序资源文件和生命周期。
应用程序环境
每个应用程序都有自己的环境。环境是一个关键字列表,它将原子映射到项。请注意,此环境与操作系统环境无关。
默认情况下,应用程序的环境是一个空列表。在 Mix 项目的 mix.exs
文件中,您可以在 application/0
中设置 :env
键
def application do
[env: [db_host: "localhost"]]
end
现在,在您的应用程序中,您可以使用 fetch_env!/2
等函数来读取此环境
defmodule MyApp.DBClient do
def start_link() do
SomeLib.DBClient.start_link(host: db_host())
end
defp db_host do
Application.fetch_env!(:my_app, :db_host)
end
end
在 Mix 项目中,可以通过 config/config.exs
和 config/runtime.exs
文件覆盖应用程序及其依赖项的环境。前者在构建时加载,在代码编译之前加载,而后者在运行时加载,就在应用程序启动之前加载。例如,使用您应用程序的人员可以按如下方式覆盖其 :db_host
环境变量
import Config
config :my_app, :db_host, "db.local"
有关更多信息,请参阅 Mix
模块中的“配置”部分。您还可以使用 put_env/3
和 delete_env/2
等函数动态更改应用程序环境。
库中的环境
库很少使用
config/config.exs
和config/runtime.exs
配置文件。库通常在其mix.exs
的application/0
函数中定义其环境。配置文件通常由应用程序用于配置其库。
读取其他应用程序的环境
每个应用程序负责自己的环境。不要使用此模块中的函数直接访问或修改其他应用程序的环境。每当您更改应用程序环境时,Elixir 的构建工具只会重新编译属于该应用程序的文件。因此,如果您读取了另一个应用程序的应用程序环境,则您可能依赖于过时的配置,因为您的文件不会随着它的更改而重新编译。
编译时环境
在前面的示例中,我们在运行时读取了应用程序环境
defmodule MyApp.DBClient do
def start_link() do
SomeLib.DBClient.start_link(host: db_host())
end
defp db_host do
Application.fetch_env!(:my_app, :db_host)
end
end
换句话说,应用程序 :my_app
的环境键 :db_host
只有在 MyApp.DBClient
实际启动时才会读取。虽然在运行时读取应用程序环境是首选方法,但在某些罕见的情况下,您可能希望使用应用程序环境来配置特定项目的编译。但是,如果您尝试在函数之外访问 Application.fetch_env!/2
defmodule MyApp.DBClient do
@db_host Application.fetch_env!(:my_app, :db_host)
def start_link() do
SomeLib.DBClient.start_link(host: @db_host)
end
end
您可能会看到警告和错误
warning: Application.fetch_env!/2 is discouraged in the module body,
use Application.compile_env/3 instead
iex:3: MyApp.DBClient
** (ArgumentError) could not fetch application environment :db_host
for application :my_app because the application was not loaded nor
configured
发生这种情况是因为在定义模块时,应用程序环境尚未可用。幸运的是,警告告诉我们如何解决此问题,方法是使用 Application.compile_env/3
defmodule MyApp.DBClient do
@db_host Application.compile_env(:my_app, :db_host, "db.local")
def start_link() do
SomeLib.DBClient.start_link(host: @db_host)
end
end
这里的区别在于,compile_env
期望默认值作为参数给出,而不是使用 mix.exs
的 def application
函数。此外,通过使用 compile_env/3
,Mix 等工具会在编译期间存储使用的值,并在您的系统启动时将编译值与运行时值进行比较,如果它们不同,则会引发错误。
无论如何,应避免使用编译时环境。只要有可能,在运行时读取应用程序环境应该是首选。
应用程序回调模块
应用程序可以加载、启动和停止。通常,Mix 等构建工具会为您处理启动应用程序及其所有依赖项,但您也可以通过调用以下方法手动执行此操作
{:ok, _} = Application.ensure_all_started(:some_app)
当应用程序启动时,开发人员可以配置一个执行自定义代码的回调模块。开发人员使用此回调来启动应用程序监督树。
第一步是在 mix.exs
文件中的 application/0
定义中添加一个 :mod
键。它期望一个元组,其中包含应用程序回调模块和启动参数(通常是空列表)
def application do
[mod: {MyApp, []}]
end
传递给 :mod
的 MyApp
模块需要实现 Application
行为。这可以通过在该模块中放置 use Application
并实现 start/2
回调来完成,例如
defmodule MyApp do
use Application
def start(_type, _args) do
children = []
Supervisor.start_link(children, strategy: :one_for_one)
end
end
use Application
当您使用
use Application
时,Application
模块将设置@behaviour Application
并为stop/1
函数定义一个可重写定义,这是 Erlang/OTP 所必需的。
The start/2
回调必须生成并链接一个监督器,并返回 {:ok, pid}
或 {:ok, pid, state}
,其中 pid
是监督器的 PID,state
是可选的应用程序状态。 args
是传递给 :mod
选项的元组的第二个元素。
传递给 start/2
的 type
参数通常是 :normal
,除非在分布式设置中配置了应用程序接管和故障转移。分布式应用程序超出了本文档的范围。
当应用程序关闭时,其 stop/1
回调将在运行时停止监督树后调用。此回调允许应用程序进行任何最终清理。该参数是 start/2
返回的状态(如果有),否则为 []
。 stop/1
的返回值被忽略。
通过使用 Application
,模块将获得 stop/1
的默认实现,该实现会忽略其参数并返回 :ok
,但可以被重写。
应用程序回调模块还可以实现可选回调 prep_stop/1
。如果存在,则在终止监督树之前调用 prep_stop/1
。它的参数是 start/2
返回的状态(如果有),否则为 []
,其返回值传递给 stop/1
。
应用程序资源文件
在上面的部分中,我们已经在 mix.exs
文件的 application/0
部分中配置了一个应用程序。最终,Mix 将使用此配置创建一个 应用程序资源文件,该文件名为 APP_NAME.app
。例如,OTP 应用程序 ex_unit
的应用程序资源文件名为 ex_unit.app
。
您可以在 Mix.Tasks.Compile.App
的文档中了解有关应用程序资源文件生成的更多信息,也可以通过运行 mix help compile.app
来了解。
应用程序生命周期
加载应用程序
应用程序被加载,这意味着运行时会找到并处理它们的资源文件
Application.load(:ex_unit)
#=> :ok
当加载应用程序时,其资源文件中指定的环境将与其配置文件中的任何覆盖合并。
加载应用程序不会加载其模块。
实际上,您很少手动加载应用程序,因为这是启动过程的一部分,将在下面解释。
启动应用程序
应用程序也被启动
Application.start(:ex_unit)
#=> :ok
在应用程序编译完成后,运行系统就是启动当前应用程序及其依赖项的问题。与其他语言不同,Elixir 没有负责启动系统的 main
过程。相反,您启动一个或多个应用程序,每个应用程序都有自己的初始化和终止逻辑。
当应用程序启动时,如果 Application.load/1
尚未调用,则会自动调用它。然后,它会检查其资源文件的 applications
键中列出的依赖项是否已启动。至少有一个依赖项未启动是一个错误条件。诸如 ensure_all_started/1
等函数会为您处理启动应用程序及其所有依赖项。
如果应用程序没有配置回调模块,则此时会完成启动。否则,将调用其 start/2
回调。此函数返回的顶级监督器的 PID 会被运行时存储以供以后使用,并且如果存在,返回的应用程序状态也会被保存。
停止应用程序
启动的应用程序最终会被停止
Application.stop(:ex_unit)
#=> :ok
停止没有回调模块的应用程序是定义的,但除了某些系统跟踪之外,实际上它是一个空操作。
停止具有回调模块的应用程序包含三个步骤
- 如果存在,则调用可选回调
prep_stop/1
。 - 终止顶级监督器。
- 调用必需回调
stop/1
。
传递给回调的参数与 start/2
可选返回的状态有关,并在上面关于回调模块的部分中进行了说明。
需要注意的是,步骤 2 是一个阻塞步骤。终止一个主管会触发一个递归的子进程终止链,从而有序地关闭所有后代进程。只有在整个监督树终止后才会调用 stop/1
回调。
要干净地关闭一个正在运行的系统,可以调用 System.stop/1
。它会按照应用程序启动的顺序反向关闭所有应用程序。
默认情况下,来自操作系统的 SIGTERM 会自动转换为 System.stop/0
。您还可以通过 :os.set_signal/2
函数对操作系统信号进行更明确的控制。
工具
Mix 构建工具自动化了大部分应用程序管理任务。例如,mix test
会自动启动您的应用程序依赖项和应用程序本身,然后运行您的测试。 mix run --no-halt
会启动您的当前项目,可用于启动一个长期运行的系统。请参阅 mix help run
。
开发人员还可以使用 mix release
构建**发布版**。发布版能够将所有源代码以及 Erlang VM 打包到一个单一目录中。发布版还允许您明确控制每个应用程序的启动方式和顺序。它们还提供了一种更简化的机制来启动和停止系统、调试、记录以及系统监控。
最后,Elixir 提供了 escripts 和 archives 等工具,它们是用于打包应用程序的不同机制。这些工具通常用于开发人员之间共享工具,而不是作为部署选项。请参阅 mix help archive.build
和 mix help escript.build
以获取更多详细信息。
更多信息
有关应用程序的更多详细信息,请查看 :application
Erlang 模块 的文档以及 应用程序 部分的 OTP 设计原则用户指南。
摘要
函数
获取 app 的目录。
返回 app_dir/1
内部的给定路径。
在编译时读取应用程序环境。
从宏中编译时读取应用程序环境。
在编译时读取应用程序环境或引发异常。
从宏中编译时读取应用程序环境或引发异常。
从给定 app
环境中删除 key
。
确保给定的 app
或 apps
以及它们的子应用程序已启动。
确保给定的 app
已加载。
确保给定的 app
已使用 restart_type/0
启动。
返回 app
环境中 key
的值,以元组形式。
返回 app
环境中 key
的值。
格式化由 start/2
、ensure_started/2
、stop/1
、load/1
和 unload/1
返回的错误原因,返回字符串。
返回 app
的所有键值对。
获取给定模块的应用程序。
返回 app
环境中 key
的值。
加载给定的 app
。
返回一个列表,其中包含有关已加载应用程序的信息。
同时为多个应用程序设置环境。
为给定的 app
设置 key
中的 value
。
返回 app
的规范。
返回 app
规范中 key
的值。
使用 restart_type/0
启动给定的 app
。
返回一个列表,其中包含有关当前正在运行的应用程序的信息。
停止给定的 app
。
卸载给定的 app
。
类型
@type app() :: atom()
@type application_key() ::
:start_phases
| :mod
| :applications
| :optional_applications
| :included_applications
| :registered
| :maxT
| :maxP
| :modules
| :vsn
| :id
| :description
@type key() :: atom()
@type restart_type() :: :permanent | :transient | :temporary
指定应用程序的类型
:permanent
- 如果app
终止,所有其他应用程序和整个节点也会终止。:transient
- 如果app
以:normal
原因终止,则会报告该事件,但不会终止其他应用程序。如果一个瞬态应用程序异常终止,所有其他应用程序和整个节点也会终止。:temporary
- 如果app
终止,则会报告该事件,但不会终止其他应用程序(默认值)。
请注意,始终可以通过调用 stop/1
来显式停止应用程序。无论应用程序的类型如何,都不会影响其他应用程序。
还要注意,:transient
类型几乎没有实际用途,因为当一个监督树终止时,其原因被设置为 :shutdown
,而不是 :normal
。
@type state() :: term()
@type value() :: term()
回调
@callback config_change(changed, new, removed) :: :ok when changed: keyword(), new: keyword(), removed: [atom()]
代码升级后调用此回调,如果应用程序环境已更改。
changed
是应用程序环境中键及其更改值的关键字列表。 new
是包含所有新键及其值的关键字列表。 removed
是包含所有已删除键的列表。
在停止应用程序之前调用。
此函数在顶级主管终止之前调用。它接收由 start/2
返回的状态(如果有),否则接收 []
。返回值随后会传递给 stop/1
。
@callback start(start_type(), start_args :: term()) :: {:ok, pid()} | {:ok, pid(), state()} | {:error, reason :: term()}
在启动应用程序时调用。
使用 Application.start/2
(以及基于该函数的函数,例如 Application.ensure_started/2
)启动应用程序时会调用此函数。此函数应启动应用程序的顶级进程(如果应用程序遵循围绕监督的 OTP 设计原则,则该进程应该是应用程序监督树的顶级主管)。
start_type
定义了应用程序的启动方式
:normal
- 如果启动是正常启动,或者应用程序是分布式的,并且由于从另一个节点的故障转移而在当前节点上启动,并且应用程序规范键:start_phases
为:undefined
,则使用此方式。{:takeover, node}
- 如果应用程序是分布式的,并且由于在节点node
上的故障转移而在当前节点上启动,则使用此方式。{:failover, node}
- 如果应用程序是分布式的,并且由于在节点node
上的故障转移而在当前节点上启动,并且应用程序规范键:start_phases
不为:undefined
,则使用此方式。
start_args
是传递给应用程序在 :mod
规范键中的参数(例如,mod: {MyApp, [:my_args]}
)。
此函数应返回 {:ok, pid}
,或者如果启动成功,则返回 {:ok, pid, state}
。 pid
应为主管的 PID。 state
可以是任意项,如果省略,则默认为 []
;如果应用程序随后停止,则 state
会传递给 stop/1
回调(有关更多信息,请参阅 stop/1
回调的文档)。
use Application
不提供 start/2
回调的默认实现。
@callback start_phase(phase :: term(), start_type(), phase_args :: term()) :: :ok | {:error, reason :: term()}
以同步阶段启动应用程序。
此函数在 start/2
完成但 Application.start/2
尚未返回之前调用。它会为应用程序(以及任何包含的应用程序)规范中定义的每个启动阶段调用一次,调用顺序与它们在规范中的列表顺序相同。
在应用程序停止后调用。
在应用程序停止后,即在它的监督树停止后会调用此函数。它应该执行 start/2
回调所做的操作的反向操作,并执行任何必要的清理。此回调的返回值将被忽略。
state
是由 start/2
返回的状态(如果有),否则接收 []
。如果存在可选的 prep_stop/1
回调,则 state
为其返回值。
use Application
定义了此函数的默认实现,它不执行任何操作,只返回 :ok
。
函数
获取 app 的目录。
此信息根据代码路径返回。以下是一个示例
File.mkdir_p!("foo/ebin")
Code.prepend_path("foo/ebin")
Application.app_dir(:foo)
#=> "foo"
即使目录为空且没有 .app
文件,它也被认为是基于名称 "foo/ebin" 的应用程序目录。该名称可能包含一个连字符 -
,它被认为是应用程序版本,在查找目的时会被删除
File.mkdir_p!("bar-123/ebin")
Code.prepend_path("bar-123/ebin")
Application.app_dir(:bar)
#=> "bar-123"
返回 app_dir/1
内部的给定路径。
如果 path
是一个字符串,那么它将被用作 app_dir/1
内部的路径。如果 path
是一个字符串列表,它将被连接(参见 Path.join/1
),结果将被用作 app_dir/1
内部的路径。
示例
File.mkdir_p!("foo/ebin")
Code.prepend_path("foo/ebin")
Application.app_dir(:foo, "my_path")
#=> "foo/my_path"
Application.app_dir(:foo, ["my", "nested", "path"])
#=> "foo/my/nested/path"
在编译时读取应用程序环境。
类似于 get_env/3
,但它必须用于在编译时读取值。这允许 Elixir 跟踪编译时和运行时之间的配置值变化。
第一个参数是应用程序名称。第二个参数 key_or_path
是一个原子键或一个路径,用于在配置中遍历,从一个原子键开始。
例如,想象以下配置
config :my_app, :key, [foo: [bar: :baz]]
我们可以在编译时访问它,如下所示
Application.compile_env(:my_app, :key)
#=> [foo: [bar: :baz]]
Application.compile_env(:my_app, [:key, :foo])
#=> [bar: :baz]
Application.compile_env(:my_app, [:key, :foo, :bar])
#=> :baz
还可以将默认值作为第三个参数提供。如果路径中任何键丢失,则使用默认值
Application.compile_env(:my_app, [:unknown, :foo, :bar], :default)
#=> :default
Application.compile_env(:my_app, [:key, :unknown, :bar], :default)
#=> :default
Application.compile_env(:my_app, [:key, :foo, :unknown], :default)
#=> :default
提供路径有助于让 Elixir 知道大型配置中的哪些路径依赖于编译时。
从宏中编译时读取应用程序环境。
通常,开发人员会使用 compile_env/3
。此函数只能从旨在动态读取编译环境的宏中调用。
它将 Macro.Env
作为第一个参数,其中 Macro.Env
通常是宏中的 __CALLER__
。如果 Macro.Env
来自一个函数,则会引发异常。
在编译时读取应用程序环境或引发异常。
这与 compile_env/3
相同,但如果配置不可用,它会引发 ArgumentError
。
@spec compile_env!(Macro.Env.t(), app(), key() | list()) :: value()
从宏中编译时读取应用程序环境或引发异常。
通常,开发人员会使用 compile_env!/2
。此函数只能从旨在动态读取编译环境的宏中调用。
它将 Macro.Env
作为第一个参数,其中 Macro.Env
通常是宏中的 __CALLER__
。如果 Macro.Env
来自一个函数,则会引发异常。
从给定 app
环境中删除 key
。
它接收与 put_env/4
相同的选项。返回 :ok
。
@spec ensure_all_started(app() | [app()], type: restart_type(), mode: :serial | :concurrent ) :: {:ok, [app()]} | {:error, term()}
@spec ensure_all_started(app() | [app()], restart_type()) :: {:ok, [app()]} | {:error, term()}
确保给定的 app
或 apps
以及它们的子应用程序已启动。
第二个参数是 t:restart_type/1
(与 start/2
保持一致)或一个关键字列表。
选项
:type
- 应用程序是否应该以:permanent
、:temporary
或:transient
模式启动。有关更多信息,请参见t:restart_type/1
。:mode
- (自 v1.15.0 起) 应用程序是否应该串行或并发启动。此选项需要 Erlang/OTP 26+。
确保给定的 app
已加载。
与 load/1
相同,但如果应用程序已加载,则返回 :ok
。
@spec ensure_started(app(), restart_type()) :: :ok | {:error, term()}
确保给定的 app
已使用 restart_type/0
启动。
与 start/2
相同,但如果应用程序已启动,则返回 :ok
。
返回 app
环境中 key
的值,以元组形式。
如果配置参数不存在,则函数返回 :error
。
警告
您必须使用此函数来读取您自己的应用程序环境。不要读取其他应用程序的环境。
信息中的应用程序环境
如果您正在编写一个供其他开发人员使用的库,通常建议避免使用应用程序环境,因为应用程序环境实际上是一个全局存储。有关更多信息,请阅读我们的 库指南。
返回 app
环境中 key
的值。
如果配置参数不存在,则会引发 ArgumentError
。
警告
您必须使用此函数来读取您自己的应用程序环境。不要读取其他应用程序的环境。
信息中的应用程序环境
如果您正在编写一个供其他开发人员使用的库,通常建议避免使用应用程序环境,因为应用程序环境实际上是一个全局存储。有关更多信息,请阅读我们的 库指南。
格式化由 start/2
、ensure_started/2
、stop/1
、load/1
和 unload/1
返回的错误原因,返回字符串。
返回 app
的所有键值对。
获取给定模块的应用程序。
通过分析所有已加载应用程序的规范来定位应用程序。如果模块未列在任何应用程序规范中,则返回 nil
。
返回 app
环境中 key
的值。
如果配置参数不存在,则函数返回 default
值。
警告
您必须使用此函数来读取您自己的应用程序环境。不要读取其他应用程序的环境。
库中的应用程序环境
如果您正在编写一个供其他开发人员使用的库,通常建议避免使用应用程序环境,因为应用程序环境实际上是一个全局存储。有关更多信息,请阅读我们的 库指南。
示例
get_env/3
通常用于读取 OTP 应用程序的配置。由于 Mix 配置通常用于配置应用程序,因此我们将以此作为说明点。
考虑一个新的应用程序 :my_app
。 :my_app
包含一个数据库引擎,它支持一组数据库。数据库引擎需要知道每个数据库的配置,这些配置由 :my_app
环境中的键值对提供。
config :my_app, Databases.RepoOne,
# A database configuration
ip: "localhost",
port: 5433
config :my_app, Databases.RepoTwo,
# Another database configuration (for the same OTP app)
ip: "localhost",
port: 20717
config :my_app, my_app_databases: [Databases.RepoOne, Databases.RepoTwo]
由 :my_app
使用的数据库引擎需要知道哪些数据库存在以及数据库配置是什么。数据库引擎可以调用 Application.get_env(:my_app, :my_app_databases, [])
来检索数据库列表(由模块名称指定)。
然后,引擎可以遍历列表中的每个存储库并调用 Application.get_env(:my_app, Databases.RepoOne)
等等来检索每个存储库的配置。在这种情况下,每个配置都将是一个关键字列表,因此您可以使用 Keyword
模块甚至 Access
模块中的函数来遍历它,例如
config = Application.get_env(:my_app, Databases.RepoOne)
config[:ip]
加载给定的 app
。
为了被加载,一个 .app
文件必须位于加载路径中。所有 :included_applications
也将被加载。
加载应用程序不会启动它或加载它的模块,但它会加载它的环境。
返回一个列表,其中包含有关已加载应用程序的信息。
同时为多个应用程序设置环境。
给定的配置不应
- 将同一个应用程序列出多次
- 将同一个应用程序中的同一个键列出多次
如果未满足这些条件,它将引发异常。
它接收与 put_env/4
相同的选项。返回 :ok
。
为给定的 app
设置 key
中的 value
。
选项
:timeout
- 更改的超时时间(默认值为5_000
毫秒):persistent
- 在应用程序加载和重新加载时持久化给定的值
如果在应用程序加载之前调用 put_env/4
,则 .app
文件中指定的应用程序环境值将覆盖之前设置的值。
当需要保证使用此函数设置的参数不会被应用程序资源文件中定义的参数在加载时覆盖时,可以将 :persistent
选项设置为 true
。这意味着持久化值将在应用程序加载后以及应用程序重新加载时保留。
@spec spec(app()) :: [{application_key(), value()}] | nil
返回 app
的规范。
返回以下键
:description
:id
:vsn
:modules
:maxP
:maxT
:registered
:included_applications
:optional_applications
:applications
:mod
:start_phases
有关所有字段的说明,请参见 Erlang 的应用程序规范。
请注意,环境不会返回,因为它可以通过 fetch_env/2
访问。如果应用程序未加载,则返回 nil
。
@spec spec(app(), application_key()) :: value() | nil
返回 app
规范中 key
的值。
有关支持的键,请参见 spec/1
。如果给定的规范参数不存在,此函数将引发异常。如果应用程序未加载,则返回 nil
。
@spec start(app(), restart_type()) :: :ok | {:error, term()}
使用 restart_type/0
启动给定的 app
。
如果 app
未加载,则应用程序将首先使用 load/1
加载。在 .app
文件的 :included_applications
键中定义的任何包含的应用程序也将被加载,但它们不会被启动。
此外,在启动此应用程序之前,必须显式启动 :applications
键中列出的所有应用程序。否则,将返回 {:error, {:not_started, app}}
,其中 app
是缺少的应用程序的名称。
如果您想自动加载和启动所有 app
的依赖项,请参见 ensure_all_started/2
。
返回一个列表,其中包含有关当前正在运行的应用程序的信息。
停止给定的 app
。
停止后,应用程序仍处于加载状态。
卸载给定的 app
。
它还会卸载所有 :included_applications
。请注意,此函数不会清除应用程序模块。