[Erlang 0095] 善用 Erlang module_info


 
  在.net里面我們可以使用Attribute和反射在運行時完成對程序集元數據的解析; 下面是C#寫得一個簡單的例子:
 
 Worker1 worker1 = new Worker1 ();
  var attribute = worker1.GetType().GetCustomAttribute(typeof( ProcessOrderAttribute)) as ProcessOrderAttribute ;
  Console.WriteLine("Description {0}  Order {1}" , attribute.Descrption, attribute.Order);
  Console.ReadLine();

[ProcessOrder( " first step", 1)]
 public class Worker1
 {
 }

class ProcessOrderAttribute : Attribute
   {
       public string Descrption { get; set; }
       public int Order { get; set; }

       public ProcessOrderAttribute(string description, int order)
       {
           Descrption = description;
           Order = order;
       }
   }

 

   

  類似的在Erlang中,也可以做類似的事情,我們可以通過module_info獲取模塊的元數據,比如:
 
(rabbit@nimbus)4> test:module_info().
[{exports,[{module_info,0},{module_info,1}]},
{imports,[]},
{attributes,[{vsn,[64335248162526234078446821625873662118]},
              {tag,[generated_by_tool]}]},
{compile,[{options,[{d,use_specs},
                     {outdir,"/data/rabbitmq-server-3.0.0/ebin"},
                     {i,"/data/rabbitmq-server-3.0.0/include"},
                     debug_info]},
           {version,"4.8"},
           {time,{2012,12,12,14,39,42}},
           {source,"/data/rabbitmq-server-3.0.0/src/test.erl"}]}]
(rabbit@nimbus)5>

 

  在RabbitMQ中就使用元數據 rabbit_boot_step來控制啟動的步驟,比如在下面是rabbit.erl中的代碼片段,每一個rabbit_boot_step都定義了啟動的mfa,前置條件,描述信息等等,在rabbit啟動的時候,會根據模塊的元素信息按順序完成啟動.
 
-rabbit_boot_step({rabbit_log,
                   [{description, "logging server"},
                    {mfa,         {rabbit_sup, start_restartable_child,
                                   [rabbit_log]}},
                    {requires,    external_infrastructure},
                    {enables,     kernel_ready}]}).

-rabbit_boot_step({rabbit_event,
                   [{description, "statistics event manager"},
                    {mfa,         {rabbit_sup, start_restartable_child,
                                   [rabbit_event]}},
                    {requires,    external_infrastructure},
                    {enables,     kernel_ready}]}).

-rabbit_boot_step({kernel_ready,
                   [{description, "kernel ready"},
                    {requires,    external_infrastructure}]}).

 

  在rabbit.erl模塊可以看到rabbitmq是如何通過解析模塊元數據並完成.

start(normal, []) ->
    case erts_version_check() of
        ok ->
            {ok, Vsn} = application:get_key(rabbit, vsn),
            error_logger:info_msg("Starting RabbitMQ ~s on Erlang ~s~n",
                                  [Vsn, erlang:system_info(otp_release)]),
            {ok, SupPid} = rabbit_sup:start_link(),
            true = register(rabbit, self()),
            print_banner(),
            [ok = run_boot_step(Step) || Step <- boot_steps()],
            io:format("~nbroker running~n"),
            {ok, SupPid};
        Error ->
            Error
    end.
 
run_boot_step({StepName, Attributes}) ->
    Description = case lists:keysearch(description, 1, Attributes) of
                      {value, {_, D}} -> D;
                      false           -> StepName
                  end,
    case [MFA || {mfa, MFA} <- Attributes] of
        [] ->
            io:format("-- ~s~n", [Description]);
        MFAs ->
            io:format("starting ~-60s ...", [Description]),
            [try
                 apply(M,F,A)
             catch
                 _:Reason -> boot_error(Reason, erlang:get_stacktrace())
             end || {M,F,A} <- MFAs],
            io:format("done~n"),
            ok
    end.

boot_steps() ->
    sort_boot_steps(rabbit_misc:all_module_attributes(rabbit_boot_step)).

 

  至於rabbit_misc:all_module_attributes的實現,就簡單了,遍歷加載所有模塊的元數據即可:
  
\rabbitmq-server-3.0.0\src\rabbit_misc.erl
 
all_module_attributes(Name) ->
    Modules =
        lists:usort(
          lists:append(
            [Modules || {App, _, _}   <- application:loaded_applications(),
                        {ok, Modules} <- [application:get_key(App, modules)]])),
    lists:foldl(
      fun (Module, Acc) ->
              case lists:append([Atts || {N, Atts} <- module_attributes(Module),
                                         N =:= Name]) of
                  []   -> Acc;
                  Atts -> [{Module, Atts} | Acc]
              end
      end, [], Modules).
 
module_attributes(Module) ->
    case catch Module:module_info(attributes) of
        {'EXIT', {undef, [{Module, module_info, _} | _]}} ->
            io:format("WARNING: module ~p not found, so not scanned for boot steps.~n",
                      [Module]),
            [];
        {'EXIT', Reason} ->
            exit(Reason);
        V ->
            V
    end.

 

  調用的結果樣例:

(rabbit@nimbus)5> rabbit_misc:all_module_attributes(rabbit_boot_step).
[{rabbit_policy,[{rabbit_policy,[{description,"policy parameters"},
                                 {mfa,{rabbit_policy,register,[]}},
                                 {requires,rabbit_registry},
                                 {enables,recovery}]}]},
{rabbit_mirror_queue_misc,[{rabbit_mirror_queue_misc,[{description,"HA policy validation"},
                                                       {mfa,{rabbit_registry,register,
                                                                             [policy_validator,<<"ha-mode">>,rabbit_mirror_queue_misc]}},
                                                       {mfa,{rabbit_registry,register,
                                                                             [policy_validator,<<"ha-params">>,
                                                                              rabbit_mirror_queue_misc]}},
                                                       {requires,rabbit_registry},
                                                       {enables,recovery}]}]},
{rabbit_exchange_type_topic,[{rabbit_exchange_type_topic,[{description,"exchange type topic"},
                                                           {mfa,{rabbit_registry,register,
                                                                                 [exchange,<<"topic">>,rabbit_exchange_type_topic]}},
                                                           {requires,rabbit_registry},
........ 

 

 

關乎擴展

    如果我們需要編寫RabbitMQ的擴展,要讓知道rabbit知道我們新定義的模塊就需要進行注冊,而這個注冊就是在模塊中添加rabbit_boot_step,在Rabbit啟動的適當階段加入我們的模塊,完成注冊后在ETS的rabbit_registry表可以看到我們新注冊的模塊,比如rabbit_exchange_type_recent_history就是我們增加的新exchange.
  
  (rabbit@nimbus)4> ets:i(rabbit_registry). 
<1   > {{exchange,'x-recent-history'},rabbit_exchange_type_recent_history}
<2   > {{policy_validator,'ha-mode'},rabbit_mirror_queue_misc}
<3   > {{policy_validator,'ha-params'},rabbit_mirror_queue_misc}
<4   > {{auth_mechanism,'AMQPLAIN'},rabbit_auth_mechanism_amqplain}
<5   > {{exchange,topic},rabbit_exchange_type_topic}
<6   > {{runtime_parameter,policy},rabbit_policy}
<7   > {{auth_mechanism,'PLAIN'},rabbit_auth_mechanism_plain}
<8   > {{exchange,headers},rabbit_exchange_type_headers}
<9   > {{auth_mechanism,'RABBIT-CR-DEMO'},rabbit_auth_mechanism_cr_demo}
<10  > {{exchange,direct},rabbit_exchange_type_direct}
<11  > {{exchange,fanout},rabbit_exchange_type_fanout}
EOT  (q)uit (p)Digits (k)ill /
 
  又跟RabbitMQ學了一招,心情大好.
 
 
  今天北京大雪,不禁想起"六出飛花入戶時,坐看青竹變瓊枝"的佳句,小圖一張(小時候有一件一模一樣的棉服):
 

 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM