强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

12 - 应用详解

第 12 章:Application — OTP 应用生命周期

Application 是 OTP 中组织代码和管理生命周期的最高级别抽象。本章学习应用的启动、配置和发布。


12.1 Application 行为

12.1.1 回调模块

%% myapp_app.erl
-module(myapp_app).
-behaviour(application).

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

%% 应用启动
start(StartType, StartArgs) ->
    io:format("Starting ~p with type=~p args=~p~n",
              [?MODULE, StartType, StartArgs]),
    case myapp_sup:start_link() of
        {ok, Pid} ->
            {ok, Pid};
        Error ->
            Error
    end.

%% 应用停止前
prep_stop(State) ->
    io:format("Preparing to stop...~n"),
    State.

%% 应用停止
stop(_State) ->
    io:format("Application stopped~n"),
    ok.

%% 配置变更通知
config_change(Changed, New, Removed) ->
    io:format("Config changed: ~p new: ~p removed: ~p~n",
              [Changed, New, Removed]),
    ok.

12.1.2 StartType

类型说明
normal正常启动
{takeover, Node}从另一个节点接管
{failover, Node}主节点故障后在备用节点启动

12.1.3 应用依赖

%% src/myapp.app.src
{application, myapp, [
    {vsn, "1.0.0"},
    {mod, {myapp_app, []}},
    {applications, [
        kernel,        %% 必须
        stdlib,        %% 必须
        cowboy,        %% HTTP 服务器
        jsx,           %% JSON
        mnesia         %% 数据库
    ]},
    {included_applications, [
        %% 随主应用一起启动,但不在主应用的监督树中
    ]},
    {optional_applications, [
        %% 可选依赖,不会阻止启动
    ]}
]}.

12.2 应用管理

12.2.1 启动和停止

%% 启动应用
application:start(myapp).
application:ensure_all_started(myapp).  %% 启动所有依赖

%% 停止应用
application:stop(myapp).

%% 应用状态
application:which_applications().  %% 所有运行的应用
application:get_application().      %% 当前进程所属应用
application:get_all_env(myapp).    %% 所有配置

12.2.2 配置管理

%% config/sys.config
[
    {myapp, [
        {http_port, 8080},
        {db_host, "localhost"},
        {db_port, 5432},
        {pool_size, 10},
        {log_level, info}
    ]}
].
%% 读取配置
{ok, Port} = application:get_env(myapp, http_port).
Host = application:get_env(myapp, db_host, "127.0.0.1").

%% 动态设置配置
application:set_env(myapp, http_port, 9090).

%% 在 VM 参数中覆盖
%% erl -myapp http_port 9090

12.3 项目结构

myapp/
├── rebar.config
├── config/
│   ├── sys.config         ← 应用配置
│   └── vm.args            ← VM 参数
├── src/
│   ├── myapp.app.src      ← 应用描述
│   ├── myapp_app.erl      ← Application 回调
│   ├── myapp_sup.erl      ← 顶层 Supervisor
│   ├── myapp_server.erl   ← 业务 GenServer
│   └── myapp_handler.erl  ← 请求处理器
├── include/
│   └── myapp.hrl
├── test/
│   └── myapp_test.erl
├── priv/
│   └── static/
└── _build/

12.4 实战:完整 Web 应用

%% myweb_app.erl
-module(myweb_app).
-behaviour(application).
-export([start/2, stop/1]).

start(_StartType, _StartArgs) ->
    {ok, Port} = application:get_env(myweb, port),
    Dispatch = cowboy_router:compile([
        {'_', [
            {"/", myweb_handler, []},
            {"/api/users", myweb_users_handler, []}
        ]}
    ]),
    {ok, _} = cowboy:start_clear(myweb_http,
        [{port, Port}],
        #{env => #{dispatch => Dispatch}}
    ),
    myweb_sup:start_link().

stop(_State) ->
    cowboy:stop_listener(myweb_http),
    ok.

12.5 注意事项

⚠️ 常见问题

  1. 循环依赖:应用 A 依赖 B,B 又依赖 A → 报错
  2. 启动顺序:依赖的应用必须先启动
  3. 配置不生效:检查 sys.config 路径和格式
  4. included vs regular:included_applications 不会自动启动

💡 最佳实践

  1. 使用 application:ensure_all_started/1 启动应用及其依赖
  2. 所有配置放在 sys.config 中,避免硬编码
  3. 一个应用一个监督树
  4. 使用 rebar3 profiles 管理不同环境配置

12.6 扩展阅读


上一章:11 - 监督者详解 下一章:13 - ETS 表