查看源码 应用

建议同时阅读 Kernel 中的 appapplication 部分。

应用概念

在创建代码以实现特定功能后,您可能会考虑将其转换为应用——一个可以作为一个单元启动和停止,并且可以在其他系统中重用的组件。

创建应用的步骤如下:

  • 创建一个应用回调模块,描述如何启动和停止应用。

  • 创建一个应用规范,并将其放置在应用资源文件中。除其他事项外,此文件指定应用由哪些模块组成以及回调模块的名称。

如果您使用systools,即用于打包代码的 Erlang/OTP 工具(请参阅发布),则每个应用的代码都放置在一个单独的目录中,该目录遵循预定义的目录结构

应用回调模块

如何启动和停止应用的代码,包括其监督树,由两个回调函数描述:

start(StartType, StartArgs) -> {ok, Pid} | {ok, Pid, State}
stop(State)
  • start/2在启动应用时调用,用于通过启动顶层监督者来创建监督树。它应该返回顶层监督者的 pid 和一个可选的项,State,默认为 []。此项将原样传递给stop/1
  • StartType通常是原子normal。只有在接管或故障转移的情况下,它才具有其他值;请参阅分布式应用
  • StartArgs应用资源文件中的键mod定义。
  • stop/1在应用停止调用,用于执行任何必要的清理工作。应用的实际停止,即关闭监督树,将按照启动和停止应用中的描述自动处理。

监督者行为中打包监督树的应用回调模块示例:

-module(ch_app).
-behaviour(application).

-export([start/2, stop/1]).

start(_Type, _Args) ->
    ch_sup:start_link().

stop(_State) ->
    ok.

无法启动或停止的库应用不需要任何应用回调模块。

应用资源文件

为了定义应用,将创建一个应用规范,该规范被放入一个应用资源文件中,简称为.app文件。

{application, Application, [Opt1,...,OptN]}.
  • Application,一个原子,是应用的名称。文件必须命名为Application.app
  • 每个Opt都是一个元组{Key,Value},它定义了应用的某个属性。所有键都是可选的。对于任何省略的键,都将使用默认值。

库应用libapp的最小.app文件的内容如下:

{application, libapp, []}.

ch_app这样的监督树应用的最小.app文件ch_app.app的内容如下:

{application, ch_app,
 [{mod, {ch_app,[]}}]}.

mod定义了应用的回调模块和启动参数,在本例中分别为ch_app[]。这意味着在启动应用时将调用以下内容:

ch_app:start(normal, [])

当应用停止时,将调用以下内容:

ch_app:stop([])

当使用systools,即用于打包代码的 Erlang/OTP 工具(请参阅发布章节)时,还必须指定键descriptionvsnmodulesregisteredapplications

{application, ch_app,
 [{description, "Channel allocator"},
  {vsn, "1"},
  {modules, [ch_app, ch_sup, ch3]},
  {registered, [ch3]},
  {applications, [kernel, stdlib, sasl]},
  {mod, {ch_app,[]}}
 ]}.
  • description - 一个简短的描述,一个字符串。默认为""
  • vsn - 版本号,一个字符串。默认为""
  • modules - 此应用引入的所有模块。systools在生成启动脚本和 tar 文件时使用此列表。一个模块只能包含在一个应用中。默认为[]
  • registered - 应用中所有已注册进程的名称。systools使用此列表来检测应用之间的名称冲突。默认为[]
  • applications - 必须在此应用启动之前启动的所有应用。systools使用此列表来生成正确的启动脚本。默认为[]。请注意,所有应用都至少依赖于 Kernel 和 STDLIB。

注意

有关应用资源文件的语法和内容的详细信息,请参阅 Kernel 中的app

目录结构

当使用systools打包代码时,每个应用的代码都放置在一个单独的目录lib/Application-Vsn中,其中Vsn是版本号。

即使不使用systools,了解这一点也很有用,因为 Erlang/OTP 是按照 OTP 原则打包的,因此具有特定的目录结构。如果存在一个应用的多个版本,代码服务器(请参阅 Kernel 中的模块code)会自动使用版本号最高的目录中的代码。

开发环境的目录结构指南

只要发布的目录结构符合下面的描述,任何用于开发的目录结构都足够,但建议在开发环境中也使用相同的目录结构。版本号应从应用目录名称中省略,因为这是发布步骤的产物。

一些子目录是必需的。一些子目录是可选的,这意味着只有当应用本身需要时才应该使用它。最后,一些子目录是推荐的,这意味着建议使用它并按照此处描述的方式使用。例如,建议在应用中同时存在文档和测试,以将其视为合适的 OTP 应用。

    ─ ${application}
      ├── doc
      │   ├── internal
      │   ├── examples
      │   └── src
      ├── include
      ├── priv
      ├── src
      │   └── ${application}.app.src
      └── test
  • src - 必需的。包含 Erlang 源代码、.app 文件的源文件以及应用本身使用的内部包含文件。可以在src中使用其他子目录作为命名空间来组织源文件。这些目录的深度永远不应超过一层。
  • priv - 可选的。用于应用特定的文件。
  • include - 可选的。用于必须可从其他应用访问的公共包含文件。
  • doc - 推荐的。任何源文档都应放置在此处的子目录中。
  • doc/internal - 推荐的。任何描述此应用实现细节的文档,不打算公开发布的文档,都应放置在此处。
  • doc/examples - 推荐的。关于如何使用此应用的示例的源代码应放置在此处。建议从该目录向公共文档提供示例。
  • doc/src - 推荐的。所有文档的源文件,如 Markdown、AsciiDoc 或 XML 文件,都应放置在此处。
  • test - 推荐的。所有与测试相关的文件,如测试套件和测试规范,都应放置在此处。

可能需要开发环境中的其他目录。如果使用了 Erlang 以外的语言的源代码,例如用于 NIF 的 C 代码,则该代码应放置在单独的目录中。按照惯例,建议使用语言名称作为此类目录的前缀,例如 C 的c_src、Java 的java_src或 Go 的go_src。带有_src后缀的目录表示它是应用和编译步骤的一部分。最终的构建工件应以priv/libpriv/bin目录为目标。

priv目录保存应用在运行时需要的资产。可执行文件应位于priv/bin中,动态链接库应位于priv/lib中。其他资产可以自由地位于priv目录中,但建议以结构化的方式放置。

从其他语言生成 Erlang 代码的源文件,如 ASN.1 或 Mibs,应放置在与源语言同名的目录中,位于顶层或src中,例如asn1mibs。构建工件应放置在其各自的语言目录中,例如 Erlang 代码的src或 Java 代码的java_src

在开发环境中,发布的.app文件可以驻留在ebin目录中,但建议它是构建步骤的产物。按照惯例,使用位于src目录中的.app.src。此文件与.app文件几乎相同,但在构建步骤中会替换某些字段,例如应用版本。

目录名称不应大写。

建议省略空目录。

发布系统的目录结构

已发布的应用必须遵循特定的结构。

    ─ ${application}-${version}
      ├── bin
      ├── doc
      │   ├── html
      │   ├── man[1-9]
      │   ├── pdf
      │   ├── internal
      │   └── examples
      ├── ebin
      │   └── ${application}.app
      ├── include
      ├── priv
      │   ├── lib
      │   └── bin
      └── src
  • src - 可选的。包含 Erlang 源代码和应用本身使用的内部包含文件。
  • ebin - 必需的。包含 Erlang 目标代码,即.beam文件。.app文件也必须放置在此处。
  • priv - 可选的。用于应用特定的文件。应使用code:priv_dir/1访问此目录。
  • priv/lib - 推荐使用。应用程序使用的任何共享对象文件,例如 NIF 或链接的驱动程序,都应放在此处。
  • priv/bin - 推荐使用。应用程序使用的任何可执行文件,例如端口程序,都应放在此处。
  • include - 可选的。用于必须可从其他应用访问的公共包含文件。
  • bin - 可选。应用程序生成的任何可执行文件,例如 escripts 或 shell 脚本,都应放在此处。
  • doc - 可选。任何发布的文档都应放在此处的子目录中。

src 目录可能有助于发布以进行调试,但这不是必需的。只有当应用程序具有公共包含文件时,才应发布 include 目录。

建议省略空目录。

应用程序控制器

当 Erlang 运行时系统启动时,会启动许多进程作为 Kernel 应用程序的一部分。其中一个进程是 *应用程序控制器* 进程,注册为 application_controller

应用程序上的所有操作都由应用程序控制器协调。使用 Kernel 中的 application 模块来加载、卸载、启动和停止应用程序。

加载和卸载应用程序

在启动应用程序之前,必须先 *加载* 它。应用程序控制器从 .app 文件读取并存储信息。

1> application:load(ch_app).
ok
2> application:loaded_applications().
[{kernel,"ERTS  CXC 138 10","2.8.1.3"},
 {stdlib,"ERTS  CXC 138 10","1.11.4.3"},
 {ch_app,"Channel allocator","1"}]

可以卸载已停止或从未启动的应用程序。有关应用程序的信息将从应用程序控制器的内部数据库中删除。

3> application:unload(ch_app).
ok
4> application:loaded_applications().
[{kernel,"ERTS  CXC 138 10","2.8.1.3"},
 {stdlib,"ERTS  CXC 138 10","1.11.4.3"}]

注意

加载/卸载应用程序不会加载/卸载应用程序使用的代码。代码加载由代码服务器以通常的方式处理。

启动和停止应用程序

通过调用以下方法启动应用程序

5> application:start(ch_app).
ok
6> application:which_applications().
[{kernel,"ERTS  CXC 138 10","2.8.1.3"},
 {stdlib,"ERTS  CXC 138 10","1.11.4.3"},
 {ch_app,"Channel allocator","1"}]

如果应用程序尚未加载,则应用程序控制器首先使用 application:load/1 加载它。它会检查 applications 键的值,以确保在此应用程序之前启动的所有应用程序都在运行。

之后,应用程序控制器为该应用程序创建一个 *应用程序主进程*。

应用程序主进程将自己建立为应用程序中所有进程的 组长,并将 I/O 转发到先前的组长。

注意

应用程序主进程作为组长的目的是为了轻松跟踪属于该应用程序的进程。这是支持 application:get_application/0application:get_env/1 函数所必需的,并且在停止应用程序时也要确保属于该应用程序的所有进程都被终止。

应用程序主进程通过调用 .app 文件中 mod 键定义的模块中的应用程序回调函数 start/2 来启动应用程序,该模块具有启动参数。

通过调用以下方法停止(但不卸载)应用程序

7> application:stop(ch_app).
ok

应用程序主进程通过告诉顶层监督者关闭来停止应用程序。顶层监督者告诉其所有子进程关闭,依此类推;整个树以反向启动顺序终止。然后,应用程序主进程调用 mod 键定义的模块中的应用程序回调函数 stop/1

配置应用程序

可以使用 *配置参数* 配置应用程序。这些参数是由 .app 文件中 env 键指定的 {Par,Val} 元组列表。

{application, ch_app,
 [{description, "Channel allocator"},
  {vsn, "1"},
  {modules, [ch_app, ch_sup, ch3]},
  {registered, [ch3]},
  {applications, [kernel, stdlib, sasl]},
  {mod, {ch_app,[]}},
  {env, [{file, "/usr/local/log"}]}
 ]}.

Par 是一个原子。Val 是任何项。应用程序可以通过调用 application:get_env(App, Par) 或许多类似函数来检索配置参数的值。有关详细信息,请参阅 Kernel 中的 application 模块。

示例

% erl
Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]

Eshell V5.2.3.6  (abort with ^G)
1> application:start(ch_app).
ok
2> application:get_env(ch_app, file).
{ok,"/usr/local/log"}

.app 文件中的值可以被 *系统配置文件* 中的值覆盖。这是一个包含相关应用程序配置参数的文件。

[{Application1, [{Par11,Val11},...]},
 ...,
 {ApplicationN, [{ParN1,ValN1},...]}].

系统配置文件应命名为 Name.config,并且 Erlang 启动时应使用命令行参数 -config Name。有关详细信息,请参阅 Kernel 中的 config

示例

创建一个包含以下内容的 test.config 文件

[{ch_app, [{file, "testlog"}]}].

file 的值会覆盖 .app 文件中定义的 file 值。

% erl -config test
Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]

Eshell V5.2.3.6  (abort with ^G)
1> application:start(ch_app).
ok
2> application:get_env(ch_app, file).
{ok,"testlog"}

如果使用 版本处理,则应仅使用一个系统配置文件,并且该文件应命名为 sys.config

.app 文件中的值和系统配置文件中的值可以直接从命令行覆盖

% erl -ApplName Par1 Val1 ... ParN ValN

示例

% erl -ch_app file '"testlog"'
Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]

Eshell V5.2.3.6  (abort with ^G)
1> application:start(ch_app).
ok
2> application:get_env(ch_app, file).
{ok,"testlog"}

应用程序启动类型

启动应用程序时定义了 *启动类型*

application:start(Application, Type)

application:start(Application) 与调用 application:start(Application, temporary) 相同。类型也可以是 permanenttransient

  • 如果永久应用程序终止,则所有其他应用程序和运行时系统也会终止。
  • 如果瞬态应用程序以 normal 原因终止,则会报告此情况,但不会终止其他应用程序。如果瞬态应用程序异常终止,即因 normal 以外的任何其他原因终止,则所有其他应用程序和运行时系统也会终止。
  • 如果临时应用程序终止,则会报告此情况,但不会终止其他应用程序。

始终可以通过调用 application:stop/1 显式停止应用程序。无论模式如何,都不会影响其他应用程序。

瞬态模式的实际用途不大,因为当监督树终止时,原因设置为 shutdown,而不是 normal