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

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);
  }
}

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

(0)

相关推荐

  • Laravel框架中实现使用阿里云ACE缓存服务

    之前我写了一篇在 Laravel 4 框架中使用阿里云 OCS 缓存的文章,介绍了如何通过扩展 Laravel 4 来支持需要 SASL 认证的阿里云 OCS 缓存服务.有网友问我,ACE 的缓存怎么在 Laravel 4 中使用.我本来觉得应该可以完全用相同的办法,后来自己尝试的时候才发现,ACE 的缓存差别非常大.所以再写一篇,介绍一下如何在 Laravel 框架中使用阿里云 ACE 的缓存服务. 如何扩展 Laravel 的缓存驱动 在 Laravel 4 中使用 Cache::get($

  • Laravel中服务提供者和门面模式的入门介绍

    前言 在laravel中,我们可能需要用到自己添加的类时,可以建立一个文件夹专门存放类文件,也可以使用laravel的服务提供者的方式来使用. 这两者其实区别不大,主要是前者使用的话,会跟业务代码产生依赖,想象一下,如果一个控制器之中引用了很多自定义的类文件的话,那么可以想像会产生多少依赖,所以我们可以使用服务提供者的方式,向laravel的容器内注册类,这样的话,就能够在一个单独的配置文件里面来管理依赖,逻辑和后期维护也会方便不少. 使用门面主要是可以不需要去实例化类,可以使用静态方法的方式去

  • 详解如何在云服务器上部署Laravel

    学习PHP和Laravel已经有一段时间了,但是所有的代码都是跑在本地的虚拟主机上的,于是去腾讯云申请了一个月的免费云主机,想把项目部署到云服务器上. 不得不说这里面的坑实在是有点多,让我这个初次接触服务器的小白摸不清头脑.在配置好服务器之后,部署一个Laravel项目更是费劲心思,于是乎想记录下部署Laravel项目的过程. PS: Linux真是越用越有感觉的系统,回家在台式机上也要装个Linux敲代码用. 环境简介 在操作系统的选择上,我选用了Linux ubuntu16.04的系统,使用

  • Laravel 4.2 中队列服务(queue)使用感受

    这半个月,我参与重写了一个微信公众号后端系统,首次使用了laravel 4.2,以及laravel引以为傲的队列服务(queue). 由于整个系统涉及到多端交互,又有大量语音传输.处理的业务,我们在一些地方发现响应时间过长.之前的系统基于node.js和mongoDB,由于node天生就是异步,有守护进程,所以并没有出现过这个问题,而这次重写必然要引入异步流程了.Queue进入了我们的视线. 根据这一页几乎还全是英文的"中文文档" ,laravel恰好在4.2版本中刚刚引入了redis

  • laravel 5.3中自定义加密服务的方案详解

    前言 本文介绍的是laravel 5.3中自定义加密服务的方案,利用laravel的服务容器,实现自定义加密服务注册(示例是支持长字符串的RSA加密),下面来看看详细的介绍: 创建加密解密服务类 文件地址 /app/Service/Common/CryptService.php 代码如下 下面这个是个人写的支持长字符串的RSA加密类作为示例,自定义加密的话只需更改这个文件的代码就好,其它操作只是为了实现依赖注入. <?php namespace App\Service\Common; class

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

    1. 容器的本质 服务容器本身就是一个数组,键名就是服务名,值就是服务. 服务可以是一个原始值,也可以是一个对象,可以说是任意数据. 服务名可以是自定义名,也可以是对象的类名,也可以是接口名. // 服务容器 $container = [ // 原始值 'text' => '这是一个字符串', // 自定义服务名 'customName' => new StdClass(), // 使用类名作为服务名 'StdClass' => new StdClass(), // 使用接口名作为服务名

  • 详解如何进入、退出docker容器的方法

    1 启动docker服务 首先需要知道启动docker服务是: service docker start 或者: systemctl start docker 2 关闭docker服务 关闭docker服务是: service docker stop 或者: systemctl stop docker 3 启动docker某个image(镜像)的container(容器) Docker的镜像称为image,容器称为container. 对于Docker来说,image是静态的,类似于操作系统快照

  • 详解SpringBoot 应用如何提高服务吞吐量

    意外和明天不知道哪个先来.没有危机是最大的危机,满足现状是最大的陷阱. 背景 生产环境偶尔会有一些慢请求导致系统性能下降,吞吐量下降,下面介绍几种优化建议. 方案 1.undertow替换tomcat 电子商务类型网站大多都是短请求,一般响应时间都在100ms,这时可以将web容器从tomcat替换为undertow,下面介绍下步骤: 1.增加pom配置 <dependency> <groupid> org.springframework.boot </groupid>

  • 详解selenium + chromedriver 被反爬的解决方法

    问题背景:这个问题是在爬取某夕夕商城遇到的问题,原本的方案是用selenium + chromedriver + mitmproxy开心的刷,但是几天之后,发现刷不出来了,会直接跳转到登陆界面(很明显,是遭遇反爬了) 讲实话,这还是第一次用硒被反爬的,于是进行大规模的测试对比. 同台机器,用铬浏览器正常访问是不用跳转到登陆界面的,所以不是IP的问题.再用提琴手抓包对比了一下两个请求头,请求头都是一样的,所以忽略标头的反爬. 最后通过分析,可能是硒被检测出来了.于是就去查资料.大概的查到是和web

  • 详解SpringBoot配置文件启动时动态配置参数方法

    序言 当我们要同时启用多个项目而又要使用不同端口或者变换配置属性时,我们可以在配置文件中设置${变量名}的变量来获取启动时传入的参数,从而实现了动态配置参数,使启用项目更加灵活 例子 server: port: ${PORT:50101} #服务端口 spring: application: name: xc‐govern‐center #指定服务名 eureka: client: registerWithEureka: true #服务注册,是否将自己注册到Eureka服务中 fetchReg

  • 详解Node.js使用token进行认证的简单示例

    本文只介绍简单的应用,关于json web token的具体介绍以及原理请参考阮一峰老师的JSON Web Token 入门教程. 使用的Node框架是koa2,前端发送ajax请求使用axios 首先创建工程目录: static中存放静态资源,views存放前端模板,server.js为后端代码. 安装必要的依赖项: "dependencies": { "@koa/router": "^8.0.8", "jsonwebtoken&qu

  • 详解处理Java中的大对象的方法

    目录 String中的substring 集合大对象扩容 保持合适的对象粒度 Bitmap 把对象变小 数据的冷热分离 数据双写 写入 MQ 分发 使用 Binlog 同步 思维发散 小结 本文我们将讲解一下对于“大对象”的优化.这里的“大对象”,是一个泛化概念,它可能存放在 JVM 中,也可能正在网络上传输,也可能存在于数据库中. 那么为什么大对象会影响我们的应用性能呢? 第一,大对象占用的资源多,垃圾回收器要花一部分精力去对它进行回收: 第二,大对象在不同的设备之间交换,会耗费网络流量,以及

  • 详解python中executemany和序列的使用方法

    详解python中executemany和序列的使用方法 一 代码 import sqlite3 persons=[ ("Jim","Green"), ("Hu","jie") ] conn=sqlite3.connect(":memory:") conn.execute("CREATE TABLE person(firstname,lastname)") conn.executeman

  • 详解JavaScript中的4种类型识别方法

    具体内容如下: 1.typeof [输出]首字母小写的字符串形式 [功能] [a]可以识别标准类型(将Null识别为object) [b]不能识别具体的对象类型(Function除外) [实例] console.log(typeof "jerry");//"string" console.log(typeof 12);//"number" console.log(typeof true);//"boolean" console

  • 详解数据库多表连接查询的实现方法

    详解数据库多表连接查询的实现方法 通过连接运算符可以实现多个表查询.连接是关系数据库模型的主要特点,也是它区别于其它类型数据库管理系统的一个标志. 在关系数据库管理系统中,表建立时各数据之间的关系不必确定,常把一个实体的所有信息存放在一个表中.当检索数据时,通过连接操作查询出存放在多个表中的不同实体的信息.连接操作给用户带来很大的灵活性,他们可以在任何时候增加新的数据类型.为不同实体创建新的表,尔后通过连接进行查询. 连接可以在SELECT 语句的FROM子句或WHERE子句中建立,似是而非在F

随机推荐