当前位置: 首页 > 工具软件 > Mojolicious > 使用案例 >

Mojolicious 框架--- 源代码分析

翟修明
2023-12-01

为什么学习perl,因为perl难,为什么难了还要学,因为学会了做事情就简单了

Mojolicious 框架真的很酷,1行代码可以完成其他语言100行才能完成的事情,各种奇异符号会让你干瞪眼,看代码需要猜,需要反复,需要思考,在这之前我只会自己用perl写一些工具,不知道继承,不知道对象,也没有看书,所以我不懂得时候就盯着代码看,一直盯着,真的有效果,查单词变量的原始英文翻译,猜,真的能猜中。Mojolicious用简陋的语法创造了高超的抽象,封装,继承,多态,运行时加载,这是大师所作,我偷学成功后,希望也可以干点事情。

一开始并没有调试morbo,而是调试hypnotoad,这个家伙是多进程架构,一下就跑没了,后来通过改代码可以跳过exec,但是其他地方也甚是复杂,也没有基础,所以看到morbo比较简单,先从morbo开始吧。

perl -d ./morbo ../../examples/hello.pl

my $morbo = Mojo::Server::Morbo->new;
    创建morbo对象
$morbo->daemon->listen(\@listen) if @listen; 没有参数跳过
$morbo->backend->watch(\@watch)  if @watch; 没有参数跳过
$morbo->run($app);
  unshift @{$self->backend->watch}, $0 = $app;
  Morbo文件中有has backend属性,值是一个函数
  函数创建了Mojo::Server::Morbo::Backend::Poll对象
 
为了调试这个unshift @{$self->backend->watch}, $0 = $app;
$self是morbo对象,$self->backend触发寻找morbo对象的backend属性,于是调用了Base.pm的sub attr函数的
exists $_[0]{$attr} ? $_[0]{$attr} : ($_[0]{$attr} = $value->($_[0])),为什么执行到这里,下面的daemon属性有分析
目前morbo对象是没有backend属性的,于是执行($_[0]{$attr} = $value->($_[0]))构建backend属性
于是触发了Morbo.pm的has backend对应的函数执行,构建Mojo::Server::Morbo::Backend::Poll对象,作为morbo对象的backend属性值
于是触发了return $backend->new unless my $e = load_class $backend;意思是在load_class成功的情况下,创建一个backend对象,返回
load_class是Mojo::Loader的一个函数,调用它,参数是Mojo::Server::Morbo::Backend::Poll
于是执行return undef if $class->can('new') || eval "require $class; 1";
调试结果是执行了或运算符的后者,意思是前者为false,于是执行了require Mojo::Server::Morbo::Backend::Poll

Loader.pm:44
eval 'require Mojo::Server::Morbo::Backend::Poll; 1'
    ---->require 'Mojo/Server/Morbo/Backend/Poll.pm'
        ---->use Mojo::Base 'Mojo::Server::Morbo::Backend';
            1. eval {...} called, 2. Mojo::Server::Morbo::Backend::Poll::BEGIN() called, 3. Mojo::Base::import('Mojo::Base', 'Mojo::Server::Morbo::Backend')
            ---->use Mojo::Base -base;
                1. eval {...} called, 2. Mojo::Server::Morbo::Backend::BEGIN() called, 3. Mojo::Base::import('Mojo::Base', '-base')
                
最后调用了Base.pm的sub import函数,因为import是Base的成员函数,所以import都带有'Mojo::Base作为第一个参数,import函数上来就shift,caller是谁调用了use(不是入参)
在import Backend的过程中,调了base的import函数,中间有require(Mojo::Util::class_to_path($flags[0])) unless $flags[0]->can('new');
会触发递归找依赖,即找Backend依赖的Base,即调用require 'Mojo/Server/Morbo/Backend.pm',因为文件中有use Mojo::Base -base; 所以又调用了Mojo::Base::import('Mojo::Base', '-base'),和上次import的区别是第二个参数,只有-base
import中有if ($flags[0] eq '-base') { $flags[0] = $class },给$flags重新赋值Mojo::Base
同样会经过require(Mojo::Util::class_to_path($flags[0])) unless $flags[0]->can('new'),这里没有执行require,因为$flags[0]->can('new')为true
即Mojo::Base->can('new')为true
然后执行push @{"${caller}::ISA"}, $flags[0];即push @{"Mojo::Server::Morbo::Backend::ISA"}, Mojo::Base;(意思是Backend是Base的子类)
Mojo::Util::monkey_patch($caller, 'has', sub { attr($caller, @_) });
即Mojo::Util::monkey_patch(Mojo::Server::Morbo::Backend, 'has', sub { attr(Mojo::Server::Morbo::Backend, -base) });
 
sub monkey_patch {
  my ($class, %patch) = @_; //Mojo::Server::Morbo::Backend,has,CODE(0x325f330),patch成了字典
  no strict 'refs';
  no warnings 'redefine';
  *{"${class}::$_"} = $NAME->("${class}::$_", $patch{$_}) for keys %patch;
  //*{"Mojo::Server::Morbo::Backend::has"} = $NAME->("Mojo::Server::Morbo::Backend::has", CODE(0x325f330)})
}

在Util.pm里面定义了my $NAME = eval { require Sub::Util; Sub::Util->can('set_subname') } || sub { $_[1] };
即*{"Mojo::Server::Morbo::Backend::has"} = CODE(0x325f330)
只是赋值,没有执行这个CODE

Base.pm的import函数接着执行
$_->import for qw(strict warnings utf8);
feature->import(':5.10');

接着执行Backend.pm的
use Carp 'croak';
has watch => sub { [qw(lib templates)] };
has watch_timeout => sub { $ENV{MOJO_MORBO_TIMEOUT} || 1 }; 执行has函数
    上面给Backend添加了has属性,代码来自Base.pm的Mojo::Util::monkey_patch($caller, 'has', sub { attr($caller, @_) });
    执行sub { attr($caller, @_) });即sub { attr(Mojo::Server::Morbo::Backend, watch,CODE(0x326fc08)) });
    
    于是执行Mojo::Base::attr
        $class = Mojo::Server::Morbo::Backend,$self = Mojo::Server::Morbo::Backend,$attrs = watch, $value = CODE(0x326fc08)
        
        遍历$attrs,这里只有一个属性watch
        if (ref $value) {
          my $sub = sub {
            return
              exists $_[0]{$attr} ? $_[0]{$attr} : ($_[0]{$attr} = $value->($_[0]))
              if @_ == 1;
              如果入参@_个数是1,就在这里return,如果第一个参数(字典类型)存在$attr(key,这里是watch),直接返回其值
              不存在这个属性的话先赋值,再返回这个值,(Mojo::Server::Morbo::Backend{watch} = CODE(0x326fc08)->(Mojo::Server::Morbo::Backend))
              
            $_[0]{$attr} = $_[1];
            $_[0];
          }; 这一步是声明了一个sub函数,并没有实际调用
          Mojo::Util::monkey_patch($class, $attr, $sub);
          这一步是给Mojo::Server::Morbo::Backend添加watch属性,值是上面的函数
          Mojo::Server::Morbo::Backend::watch对应的值是CODE(0x325a6f0)
        }
        
has watch_timeout => sub { $ENV{MOJO_MORBO_TIMEOUT} || 1 }; CODE(0x325ee08)
    同样的方法给Backend添加watch_timeout属性
    即执行has属性对应的函数,执行attr,构建新的函数CODE(0x325ad50),执行monkey_patch
    
然后跳过函数定义,退出Backend.pm的执行

于是执行完了require(Mojo::Util::class_to_path($flags[0])) unless $flags[0]->can('new');
接着执行
push @{"${caller}::ISA"}, $flags[0]; 即push @{"Mojo::Server::Morbo::Backend::Poll::ISA"}, Mojo::Server::Morbo::Backend;
Mojo::Util::monkey_patch($caller, 'has', sub { attr($caller, @_) });
即Mojo::Util::monkey_patch(Mojo::Server::Morbo::Backend::Poll, 'has', sub { attr(Mojo::Server::Morbo::Backend::Poll, Mojo::Server::Morbo::Backend) });
    给Mojo::Server::Morbo::Backend::Poll增加属性has,值为sub函数

接着执行Poll.pm
use Mojo::File 'path';    没有进Base

跳过函数定义,结束Poll.pm

返回到load_class
return undef if $class->can('new') || eval "require $class; 1"; 返回了undef
 
返回到Morbo的has函数,
return $backend->new unless my $e = load_class $backend;
unless为false,所以执行$backend->new,即执行Base.pm的new,执行完后返回

到此为止执行完了$self->backend
接着执行$self->backend->watch,上面给backend添加了watch属性,对应的值是CODE(0x325a6f0)
入参@_    = Mojo::Server::Morbo::Backend::Poll=HASH(0x31f7ec0)
执行CODE(0x326fc08),即$_[0]{$attr} = CODE(0x326fc08)
即Mojo::Server::Morbo::Backend::Poll{watch} = [qw(lib templates)]

即@{$self->backend->watch}代表一个数组,返回后接着执行
unshift @{$self->backend->watch}, $0 = $app;
即把../../examples/hello.pl放到这个数组的最前面,放完后数组是[../../examples/hello.pl, lib, templates]

继续执行
$self->{modified} = 1;
$self->daemon->start->stop;
寻找Morbo对象的daemon属性,执行到Base的attr函数的exists $_[0]{$attr} ? $_[0]{$attr} : ($_[0]{$attr} = $value->($_[0])) if @_ == 1;
use引发了import,import调用了Mojo::Util::monkey_patch($caller, 'has', sub { attr($caller, @_) });,给Morbo对象增加了has属性
接下来加载Morbo.pm的时候,执行了has daemon => sub { Mojo::Server::Daemon->new };
即执行了attr(Mojo::Server::Morbo, daemon, 一段代码)
attr函数里面自定义了my $sub = ...exists...,然后用monkey_patch给Morbo对象的daemon属性设值
所以上来就执行到了Base的attr函数的exists $_[0]{$attr} ? $_[0]{$attr} : ($_[0]{$attr} = $value->($_[0])) if @_ == 1;
$_[0]{$attr}为空,所以执行$_[0]{$attr} = $value->($_[0]),
即Mojo::Server::Morbo[daemon] = sub { Mojo::Server::Daemon->new };等于这个函数的返回值
daemon既作为Morbo对象的成员函数,也作为Morbo对象的成员变量,加载时设值成员函数,实际引发调用时,由这个成员函数计算得到成员变量的值

Mojo::Server::Daemon->new
执行了Mojo::Server的sub new
Server.pm的注释说Mojo::Server是Mojo::Server::Daemon的基类
原因如下:
Mojo::Server::Daemon的开通有类似的use Mojo::Base 'Mojo::Server';
引发Base的import函数调用,import函数中有push @{"${caller}::ISA"}, $flags[0];这句话,即push @{"Mojo::Server::Daemon::ISA"}, Mojo::Server
让Server成了Daemon的子类

 

 类似资料: