侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

详解如何实现Laravel的服务容器的方法示例

Laravel  /  管理员 发布于 8年前   199

1. 容器的本质

  • 服务容器本身就是一个数组,键名就是服务名,值就是服务。
  • 服务可以是一个原始值,也可以是一个对象,可以说是任意数据。
  • 服务名可以是自定义名,也可以是对象的类名,也可以是接口名。
// 服务容器$container = [  // 原始值  'text' => '这是一个字符串',  // 自定义服务名  'customName' => new StdClass(),  // 使用类名作为服务名  'StdClass' => new StdClass(),  // 使用接口名作为服务名  'Namespace\\StdClassInterface' => new StdClass(),];// ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- //// 绑定服务到容器$container['standard'] = new StdClass();// 获取服务$standard = $container['standard'];var_dump($standard);

2. 封装成类

为了方便维护,我们把上面的数组封装到类里面。

$instances还是上面的容器数组。我们增加两个方法,instance用来绑定服务,get用来从容器中获取服务。

class BaseContainer{  // 已绑定的服务  protected $instances = [];  // 绑定服务  public function instance($name, $instance)  {    $this->instances[$name] = $instance;  }  // 获取服务  public function get($name)  {    return isset($this->instances[$name]) ? $this->instances[$name] : null;  }}// ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- //$container = new BaseContainer();// 绑定服务$container->instance('StdClass', new StdClass());// 获取服务$stdClass = $container->get('StdClass');var_dump($stdClass);

3. 按需实例化

现在我们在绑定一个对象服务的时候,就必须要先把类实例化,如果绑定的服务没有被用到,那么类就会白白实例化,造成性能浪费。

为了解决这个问题,我们增加一个bind函数,它支持绑定一个回调函数,在回调函数中实例化类。这样一来,我们只有在使用服务时,才回调这个函数,这样就实现了按需实例化。

这时候,我们获取服务时,就不只是从数组中拿到服务并返回了,还需要判断如果是回调函数,就要执行回调函数。所以我们把get方法的名字改成make。意思就是生产一个服务,这个服务可以是已绑定的服务,也可以是已绑定的回调函数,也可以是一个类名,如果是类名,我们就直接实例化该类并返回。

然后,我们增加一个新数组$bindings,用来存储绑定的回调函数。然后我们把bind方法改一下,判断下$instance如果是一个回调函数,就放到$bindings数组,否则就用make方法实例化类。

class DeferContainer extend BaseContainer{  // 已绑定的回调函数  protected $bindings = [];  // 绑定服务  public function bind($name, $instance)  {    if ($instance instanceof Closure) {      // 如果$instance是一个回调函数,就绑定到bindings。      $this->bindings[$name] = $instance;    } else {      // 调用make方法,创建实例      $this->instances[$name] = $this->make($name);    }  }  // 获取服务  public function make($name)  {    if (isset($this->instances[$name])) {      return $this->instances[$name];    }    if (isset($this->bindings[$name])) {      // 执行回调函数并返回      $instance = call_user_func($this->bindings[$name]);    } else {      // 还没有绑定到容器中,直接new.      $instance = new $name();    }    return $instance;  }}// ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- //$container = new DeferContainer();// 绑定服务$container->bind('StdClass', function () {  echo "我被执行了\n";  return new StdClass();});// 获取服务$stdClass = $container->make('StdClass');var_dump($stdClass);

StdClass这个服务绑定的是一个回调函数,在回调函数中才会真正的实例化类。如果没有用到这个服务,那回调函数就不会被执行,类也不会被实例化。

4. 单例

从上面的代码中可以看出,每次调用make方法时,都会执行一次回调函数,并返回一个新的类实例。但是在某些情况下,我们希望这个实例是一个单例,无论make多少次,只实例化一次。

这时候,我们给bind方法增加第三个参数$shared,用来标记是否是单例,默认不是单例。然后把回调函数和这个标记都存到$bindings数组里。

为了方便绑定单例服务,再增加一个新的方法singleton,它直接调用bind,并且$shared参数强制为true。

对于make方法,我们也要做修改。在执行$bindings里的回调函数以后,做一个判断,如果之前绑定时标记的shared是true,就把回调函数返回的结果存储到$instances里。由于我们是先从$instances里找服务,所以这样下次再make的时候就会直接返回,而不会再次执行回调函数。这样就实现了单例的绑定。

class SingletonContainer extends DeferContainer{  // 绑定服务  public function bind($name, $instance, $shared = false)  {    if ($instance instanceof Closure) {      // 如果$instance是一个回调函数,就绑定到bindings。      $this->bindings[$name] = [        'callback' => $instance,        // 标记是否单例        'shared' => $shared      ];    } else {      // 调用make方法,创建实例      $this->instances[$name] = $this->make($name);    }  }  // 绑定一个单例  public function singleton($name, $instance)  {    $this->bind($name, $instance, true);  }  // 获取服务  public function make($name)  {    if (isset($this->instances[$name])) {      return $this->instances[$name];    }    if (isset($this->bindings[$name])) {      // 执行回调函数并返回      $instance = call_user_func($this->bindings[$name]['callback']);      if ($this->bindings[$name]['shared']) {        // 标记为单例时,存储到服务中        $this->instances[$name] = $instance;      }    } else {      // 还没有绑定到容器中,直接new.      $instance = new $name();    }    return $instance;  }}// ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- //$container = new SingletonContainer();// 绑定服务$container->singleton('anonymous', function () {  return new class  {    public function __construct()    {      echo "我被实例化了\n";    }  };});// 无论make多少次,只会实例化一次$container->make('anonymous');$container->make('anonymous');// 获取服务$anonymous = $container->make('anonymous');var_dump($anonymous)

上面的代码用singleton绑定了一个名为anonymous的服务,回调函数里返回了一个匿名类的实例。这个匿名类在被实例化时会输出一段文字。无论我们make多少次anonymous,这个回调函数只会被执行一次,匿名类也只会被实例化一次。

5. 自动注入

自动注入是Ioc容器的核心,没有自动注入就无法做到控制反转。

自动注入就是指,在实例化一个类时,用反射类来获取__construct所需要的参数,然后根据参数的类型,从容器中找到已绑定的服务。我们只要有了__construct方法所需的所有参数,就能自动实例化该类,实现自动注入。

现在,我们增加一个build方法,它只接收一个参数,就是类名。build方法会用反射类来获取__construct方法所需要的参数,然后返回实例化结果。

另外一点就是,我们之前在调用make方法时,如果传的是一个未绑定的类,我们直接new了这个类。现在我们把未绑定的类交给build方法来构建,因为它支持自动注入。

class InjectionContainer extends SingletonContainer{  // 获取服务  public function make($name)  {    if (isset($this->instances[$name])) {      return $this->instances[$name];    }    if (isset($this->bindings[$name])) {      // 执行回调函数并返回      $instance = call_user_func($this->bindings[$name]['callback']);      if ($this->bindings[$name]['shared']) {        // 标记为单例时,存储到服务中        $this->instances[$name] = $instance;      }    } else {      // 使用build方法构建此类      $instance = $this->build($name);    }    return $instance;  }  // 构建一个类,并自动注入服务  public function build($class)  {    $reflector = new ReflectionClass($class);    $constructor = $reflector->getConstructor();    if (is_null($constructor)) {      // 没有构造函数,直接new      return new $class();    }    $dependencies = [];    // 获取构造函数所需的参数    foreach ($constructor->getParameters() as $dependency) {      if (is_null($dependency->getClass())) {        // 参数类型不是类时,无法从容器中获取依赖        if ($dependency->isDefaultValueAvailable()) {          // 查找参数的默认值,如果有就使用默认值          $dependencies[] = $dependency->getDefaultValue();        } else {          // 无法提供类所依赖的参数          throw new Exception('找不到依赖参数:' . $dependency->getName());        }      } else {        // 参数类型是类时,就用make方法构建该类        $dependencies[] = $this->make($dependency->getClass()->name);      }    }    return $reflector->newInstanceArgs($dependencies);  }}// ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- //class Redis{}class Cache{  protected $redis;  // 构造函数中依赖Redis服务  public function __construct(Redis $redis)  {    $this->redis = $redis;  }}$container = new InjectionContainer();// 绑定Redis服务$container->singleton(Redis::class, function () {  return new Redis();});// 构建Cache类$cache = $container->make(Cache::class);var_dump($cache);

6. 自定义依赖参数

现在有个问题,如果类依赖的参数不是类或接口,只是一个普通变量,这时候就无法从容器中获取依赖参数了,也就无法实例化类了。

那么接下来我们就支持一个新功能,在调用make方法时,支持传第二个参数$parameters,这是一个数组,无法从容器中获取的依赖,就从这个数组中找。

当然,make方法是用不到这个参数的,因为它不负责实例化类,它直接传给build方法。在build方法寻找依赖的参数时,就先从$parameters中找。这样就实现了自定义依赖参数。

需要注意的一点是,build方法是按照参数的名字来找依赖的,所以parameters中的键名也必须跟__construct中参数名一致。

class ParametersContainer extends InjectionContainer{  // 获取服务  public function make($name, array $parameters = [])  {    if (isset($this->instances[$name])) {      return $this->instances[$name];    }    if (isset($this->bindings[$name])) {      // 执行回调函数并返回      $instance = call_user_func($this->bindings[$name]['callback']);      if ($this->bindings[$name]['shared']) {        // 标记为单例时,存储到服务中        $this->instances[$name] = $instance;      }    } else {      // 使用build方法构建此类      $instance = $this->build($name, $parameters);    }    return $instance;  }  // 构建一个类,并自动注入服务  public function build($class, array $parameters = [])  {    $reflector = new ReflectionClass($class);    $constructor = $reflector->getConstructor();    if (is_null($constructor)) {      // 没有构造函数,直接new      return new $class();    }    $dependencies = [];    // 获取构造函数所需的参数    foreach ($constructor->getParameters() as $dependency) {      if (isset($parameters[$dependency->getName()])) {        // 先从自定义参数中查找        $dependencies[] = $parameters[$dependency->getName()];        continue;      }      if (is_null($dependency->getClass())) {        // 参数类型不是类或接口时,无法从容器中获取依赖        if ($dependency->isDefaultValueAvailable()) {          // 查找默认值,如果有就使用默认值          $dependencies[] = $dependency->getDefaultValue();        } else {          // 无法提供类所依赖的参数          throw new Exception('找不到依赖参数:' . $dependency->getName());        }      } else {        // 参数类型是类时,就用make方法构建该类        $dependencies[] = $this->make($dependency->getClass()->name);      }    }    return $reflector->newInstanceArgs($dependencies);  }}// ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- //class Redis{}class Cache{  protected $redis;  protected $name;  protected $default;  // 构造函数中依赖Redis服务和name参数,name的类型不是类,无法从容器中查找  public function __construct(Redis $redis, $name, $default = '默认值')  {    $this->redis = $redis;    $this->name = $name;    $this->default = $default;  }}$container = new ParametersContainer();// 绑定Redis服务$container->singleton(Redis::class, function () {  return new Redis();});// 构建Cache类$cache = $container->make(Cache::class, ['name' => 'test']);var_dump($cache);

提示:实际上,Laravel容器的build方法并没有第二个参数$parameters,它是用类属性来维护自定义参数。原理都是一样的,只是实现方式不一样。这里为了方便理解,不引入过多概念。

7. 服务别名

别名可以理解成小名、外号。服务别名就是给已绑定的服务设置一些外号,使我们通过外号也能找到该服务。

这个就比较简单了,我们增加一个新的数组$aliases,用来存储别名。再增加一个方法alias,用来让外部注册别名。

唯一需要我们修改的地方,就是在make时,要先从$aliases中找到真实的服务名。

class AliasContainer extends ParametersContainer{  // 服务别名  protected $aliases = [];  // 给服务绑定一个别名  public function alias($alias, $name)  {    $this->aliases[$alias] = $name;  }  // 获取服务  public function make($name, array $parameters = [])  {    // 先用别名查找真实服务名    $name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name;    return parent::make($name, $parameters);  }}// ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- //$container = new AliasContainer();// 绑定服务$container->instance('text', '这是一个字符串');// 给服务注册别名$container->alias('string', 'text');$container->alias('content', 'text');var_dump($container->make('string'));var_dump($container->make('content'));

8. 扩展绑定

有时候我们需要给已绑定的服务做一个包装,这时候就用到扩展绑定了。我们先看一个实际的用法,理解它的作用后,才看它是如何实现的。

// 绑定日志服务$container->singleton('log', new Log());// 对已绑定的服务再次包装$container->extend('log', function(Log $log){  // 返回了一个新服务  return new RedisLog($log);});

现在我们看它是如何实现的。增加一个$extenders数组,用来存放扩展器。再增加一个extend方法,用来注册扩展器。

然后在make方法返回$instance之前,按顺序依次调用之前注册的扩展器。

class ExtendContainer extends AliasContainer{  // 存放扩展器的数组  protected $extenders = [];  // 给服务绑定扩展器  public function extend($name, $extender)  {    if (isset($this->instances[$name])) {      // 已经实例化的服务,直接调用扩展器      $this->instances[$name] = $extender($this->instances[$name]);    } else {      $this->extenders[$name][] = $extender;    }  }  // 获取服务  public function make($name, array $parameters = [])  {    $instance = parent::make($name, $parameters);    if (isset($this->extenders[$name])) {      // 调用扩展器      foreach ($this->extenders[$name] as $extender) {        $instance = $extender($instance);      }    }    return $instance;  }}// ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- //class Redis{  public $name;  public function __construct($name = 'default')  {    $this->name = $name;  }  public function setName($name)  {    $this->name = $name;  }}$container = new ExtendContainer();// 绑定Redis服务$container->singleton(Redis::class, function () {  return new Redis();});// 给Redis服务绑定一个扩展器$container->extend(Redis::class, function (Redis $redis) {  $redis->setName('扩展器');  return $redis;});$redis = $container->make(Redis::class);var_dump($redis->name);

9. 上下文绑定

有时侯我们可能有两个类使用同一个接口,但希望在每个类中注入不同的实现,例如两个控制器,分别为它们注入不同的Log服务。

class ApiController{  public function __construct(Log $log)  {  }}class WebController{  public function __construct(Log $log)  {  }}

最终我们要用以下方式实现:

// 当ApiController依赖Log时,给它一个RedisLog$container->addContextualBinding('ApiController','Log',new RedisLog());// 当WebController依赖Log时,给它一个FileLog$container->addContextualBinding('WebController','Log',new FileLog());

为了更直观更方便更语义化的使用,我们把这个过程改成链式操作:

$container->when('ApiController')    ->needs('Log')    ->give(new RedisLog());

我们增加一个$context数组,用来存储上下文。同时增加一个addContextualBinding方法,用来注册上下文绑定。以ApiController为例,$context的真实模样是:

$context['ApiController']['Log'] = new RedisLog();

然后build方法实例化类时,先从上下文中查找依赖参数,就实现了上下文绑定。

接下来,看看链式操作是如何实现的。

首先定义一个类Context,这个类有两个方法,needs和give。

然后在容器中,增加一个when方法,它返回一个Context对象。在Context对象的give方法中,我们已经具备了注册上下文所需要的所有参数,所以就可以在give方法中调用addContextualBinding来注册上下文了。

class ContextContainer extends ExtendContainer{  // 依赖上下文  protected $context = [];  // 构建一个类,并自动注入服务  public function build($class, array $parameters = [])  {    $reflector = new ReflectionClass($class);    $constructor = $reflector->getConstructor();    if (is_null($constructor)) {      // 没有构造函数,直接new      return new $class();    }    $dependencies = [];    // 获取构造函数所需的参数    foreach ($constructor->getParameters() as $dependency) {      if (isset($this->context[$class]) && isset($this->context[$class][$dependency->getName()])) {        // 先从上下文中查找        $dependencies[] = $this->context[$class][$dependency->getName()];        continue;      }      if (isset($parameters[$dependency->getName()])) {        // 从自定义参数中查找        $dependencies[] = $parameters[$dependency->getName()];        continue;      }      if (is_null($dependency->getClass())) {        // 参数类型不是类或接口时,无法从容器中获取依赖        if ($dependency->isDefaultValueAvailable()) {          // 查找默认值,如果有就使用默认值          $dependencies[] = $dependency->getDefaultValue();        } else {          // 无法提供类所依赖的参数          throw new Exception('找不到依赖参数:' . $dependency->getName());        }      } else {        // 参数类型是一个类时,就用make方法构建该类        $dependencies[] = $this->make($dependency->getClass()->name);      }    }    return $reflector->newInstanceArgs($dependencies);  }  // 绑定上下文  public function addContextualBinding($when, $needs, $give)  {    $this->context[$when][$needs] = $give;  }  // 支持链式方式绑定上下文  public function when($when)  {    return new Context($when, $this);  }}class Context{  protected $when;  protected $needs;  protected $container;  public function __construct($when, ContextContainer $container)  {    $this->when = $when;    $this->container = $container;  }  public function needs($needs)  {    $this->needs = $needs;    return $this;  }  public function give($give)  {    // 调用容器绑定依赖上下文    $this->container->addContextualBinding($this->when, $this->needs, $give);  }}// ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- //class Dog{  public $name;  public function __construct($name)  {    $this->name = $name;  }}class Cat{  public $name;  public function __construct($name)  {    $this->name = $name;  }}$container = new ContextContainer();// 给Dog类设置上下文绑定$container->when(Dog::class)  ->needs('name')  ->give('小狗');// 给Cat类设置上下文绑定$container->when(Cat::class)  ->needs('name')  ->give('小猫');$dog = $container->make(Dog::class);$cat = $container->make(Cat::class);var_dump('Dog:' . $dog->name);var_dump('Cat:' . $cat->name);

10. 完整代码

class Container{  // 已绑定的服务  protected $instances = [];  // 已绑定的回调函数  protected $bindings = [];  // 服务别名  protected $aliases = [];  // 存放扩展器的数组  protected $extenders = [];  // 依赖上下文  protected $context = [];  // 绑定服务实例  public function instance($name, $instance)  {    $this->instances[$name] = $instance;  }  // 绑定服务  public function bind($name, $instance, $shared = false)  {    if ($instance instanceof Closure) {      // 如果$instance是一个回调函数,就绑定到bindings。      $this->bindings[$name] = [        'callback' => $instance,        // 标记是否单例        'shared' => $shared      ];    } else {      // 调用make方法,创建实例      $this->instances[$name] = $this->make($name);    }  }  // 绑定一个单例  public function singleton($name, $instance)  {    $this->bind($name, $instance, true);  }  // 给服务绑定一个别名  public function alias($alias, $name)  {    $this->aliases[$alias] = $name;  }  // 给服务绑定扩展器  public function extend($name, $extender)  {    if (isset($this->instances[$name])) {      // 已经实例化的服务,直接调用扩展器      $this->instances[$name] = $extender($this->instances[$name]);    } else {      $this->extenders[$name][] = $extender;    }  }  // 获取服务  public function make($name, array $parameters = [])  {    // 先用别名查找真实服务名    $name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name;    if (isset($this->instances[$name])) {      return $this->instances[$name];    }    if (isset($this->bindings[$name])) {      // 执行回调函数并返回      $instance = call_user_func($this->bindings[$name]['callback']);      if ($this->bindings[$name]['shared']) {        // 标记为单例时,存储到服务中        $this->instances[$name] = $instance;      }    } else {      // 使用build方法构建此类      $instance = $this->build($name, $parameters);    }    if (isset($this->extenders[$name])) {      // 调用扩展器      foreach ($this->extenders[$name] as $extender) {        $instance = $extender($instance);      }    }    return $instance;  }  // 构建一个类,并自动注入服务  public function build($class, array $parameters = [])  {    $reflector = new ReflectionClass($class);    $constructor = $reflector->getConstructor();    if (is_null($constructor)) {      // 没有构造函数,直接new      return new $class();    }    $dependencies = [];    // 获取构造函数所需的参数    foreach ($constructor->getParameters() as $dependency) {      if (isset($this->context[$class]) && isset($this->context[$class][$dependency->getName()])) {        // 先从上下文中查找        $dependencies[] = $this->context[$class][$dependency->getName()];        continue;      }      if (isset($parameters[$dependency->getName()])) {        // 从自定义参数中查找        $dependencies[] = $parameters[$dependency->getName()];        continue;      }      if (is_null($dependency->getClass())) {        // 参数类型不是类或接口时,无法从容器中获取依赖        if ($dependency->isDefaultValueAvailable()) {          // 查找默认值,如果有就使用默认值          $dependencies[] = $dependency->getDefaultValue();        } else {          // 无法提供类所依赖的参数          throw new Exception('找不到依赖参数:' . $dependency->getName());        }      } else {        // 参数类型是一个类时,就用make方法构建该类        $dependencies[] = $this->make($dependency->getClass()->name);      }    }    return $reflector->newInstanceArgs($dependencies);  }  // 绑定上下文  public function addContextualBinding($when, $needs, $give)  {    $this->context[$when][$needs] = $give;  }  // 支持链式方式绑定上下文  public function when($when)  {    return new Context($when, $this);  }}class Context{  protected $when;  protected $needs;  protected $container;  public function __construct($when, Container $container)  {    $this->when = $when;    $this->container = $container;  }  public function needs($needs)  {    $this->needs = $needs;    return $this;  }  public function give($give)  {    // 调用容器绑定依赖上下文    $this->container->addContextualBinding($this->when, $this->needs, $give);  }}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

您可能感兴趣的文章:

  • Laravel中服务提供者和门面模式的入门介绍
  • 详解如何在云服务器上部署Laravel
  • laravel 5.3中自定义加密服务的方案详解
  • Laravel框架中实现使用阿里云ACE缓存服务
  • Laravel 4.2 中队列服务(queue)使用感受


  • 上一条:
    Laravel如何创建服务器提供者实例代码
    下一条:
    laravel 事件/监听器实例代码
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • Laravel 11.15版本发布 - Eloquent Builder中添加的泛型(0个评论)
    • Laravel 11.14版本发布 - 新的字符串助手和ServeCommand改进(0个评论)
    • Laravel 11.12版本发布 - Artisan的`make`命令自动剪切`.php `扩展(0个评论)
    • Laravel的轻量型购物车扩展包:binafy/laravel-cart(0个评论)
    • Laravel 11.11版本发布 - 查看模型中的第三方关系:show(0个评论)
    • 近期文章
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-07
    • 2017-08
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-08
    • 2020-09
    • 2020-10
    • 2020-11
    • 2021-01
    • 2021-02
    • 2021-03
    • 2021-04
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-11
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-03
    • 2022-04
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    • 2024-05
    • 2024-06
    • 2024-07
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客