[Erlang 0095] 善用 Erlang module_info

来源:互联网 发布:周杰被黑真相知乎 编辑:程序博客网 时间:2024/05/17 05:17
 在.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学了一招,心情大好.
 
 
  今天北京大雪,不禁想起"六出飞花入户时,坐看青竹变琼枝"的佳句,小图一张(小时候有一件一模一样的棉服):
 


原创粉丝点击