内容简介:如果没有全局的异常错误拦截器,那我们在每个可能发生错误异常的业务逻辑分支中,都要使用 try ... catch,然后将执行结果返回 Controller层,再由其根据结果来构造相应的 Response,那代码冗余的会相当可以。全局异常错误处理,是每个框架都应该具备的,这次我们就通过简析 Laravel 的源码和执行流程,来看一下此模式是如何被运用的。
如果没有全局的异常错误拦截器,那我们在每个可能发生错误异常的业务逻辑分支中,都要使用 try ... catch,然后将执行结果返回 Controller层,再由其根据结果来构造相应的 Response,那代码冗余的会相当可以。
全局异常错误处理,是每个框架都应该具备的,这次我们就通过简析 Laravel 的源码和执行流程,来看一下此模式是如何被运用的。
源码解析
laravel/laravel 脚手架中有一个预定义好的异常处理器:
app/Exceptions/Handler.php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
// 不被处理的异常错误
protected $dontReport = [];
// 认证异常时不被flashed的数据
protected $dontFlash = [
'password',
'password_confirmation',
];
// 上报异常至错误driver,如日志文件(storage/logs/laravel.log),第三方日志存储分析平台
public function report(Exception $exception)
{
parent::report($exception);
}
// 将异常信息响应给客户端
public function render($request, Exception $exception)
{
return parent::render($request, $exception);
}
}
当 Laravel 处理一次请求时,在启动文件中注册了以下服务:
bootstrap/app.php
// 绑定 http 服务提供者
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
// 绑定 cli 服务提供者
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
// 这里将异常处理器的服务提供者绑定到了 `App\Exceptions\Handler::class`
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
而后进入请求捕获,处理阶段:
public/index.php
// 使用 http 服务处理请求
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
// http 服务处理捕获的请求 $requeset
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
因 Illuminate\Contracts\Http\Kernel::class 具体提供者是 App\Http\Kernel::class 继承至 Illuminate\Foundation\Http\Kernel::class ,我们去其中看 http 服务 的 handle 方法是如何处理请求的。
请求处理阶段:
Illuminate\Foundation\Http\Kernel::class 的 handle 方法对请求做一次处理,如果没有异常则分发路由,如果有异常则调用 reportException 和 renderException 方法 记录 & 渲染 异常。
具体处理者则是我们在 bootstrap/app.php 中注册绑定的 异常处理服务 Illuminate\Contracts\Debug\ExceptionHandler::class 的 report & render ,具体的服务即绑定的 App\Exceptions\Handler::class 。
public function handle($request)
{
try {
// 没有异常 则进入路由分发
$request->enableHttpMethodParameterOverride();
$response = $this->sendRequestThroughRouter($request);
} catch (Exception $e) {
// 捕获异常 则 report & render
$this->reportException($e);
$response = $this->renderException($request, $e);
} catch (Throwable $e) {
$this->reportException($e = new FatalThrowableError($e));
$response = $this->renderException($request, $e);
}
$this->app['events']->dispatch(
new Events\RequestHandled($request, $response)
);
return $response;
}
//Report the exception to the exception handler.
protected function reportException(Exception $e)
{
// 服务`Illuminate\Contracts\Debug\ExceptionHandler::class` 的 report 方法
$this->app[ExceptionHandler::class]->report($e);
}
//Render the exception to a response.
protected function renderException($request, Exception $e)
{
// 服务`Illuminate\Contracts\Debug\ExceptionHandler::class` 的 render 方法
return $this->app[ExceptionHandler::class]->render($request, $e);
}
handler 方法作为请求处理的入口,后续的路由分发,用户业务调用(controller, model)等执行的上下文依然在此方法中,故异常也能在这一层被捕获。
然后我们就可以在业务中通过 throw new CustomException($code, "错误异常描述"); 的方式将控制流程转交给全局异常处理器,由其解析异常并构建响应实体给客户端,这一模式在 Api服务 的开发中是效率极高的。
laravel 的依赖中有 symfony 这个超级棒的组件库,symfony 为我们提供了详细的 Http 异常库,我们可以直接借用这些异常类(当然也可以自定义)
laravel 有提供 abort 助手函数来实现创建一个异常错误,但主要面向 web 网站(因为laravel主要就是用来开发后台的嘛)的,对 Api
不太友好,而且看源码发现只顾及了 404 这货。
/**
* abort(401, "你需要登录")
* abort(403, "你登录了也白搭")
* abort(404, "页面找不到了")
* Throw an HttpException with the given data.
*
* @param int $code
* @param string $message
* @param array $headers
* @return void
*
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
*/
public function abort($code, $message = '', array $headers = [])
{
if ($code == 404) {
throw new NotFoundHttpException($message);
}
throw new HttpException($code, $message, null, $headers);
}
即只有 404 用了具体的异常类去抛出,其他的状态码都一股脑的归为 HttpException,这样就不太方便我们在全局异常处理器的 render 中根据 Exception 的具体类型来分而治之了,但 abort 也的确是为了方便你调用具体的错误页面的 resources/views/errors/{statusCode.blade.php} 的,需要对 Api 友好自己改写吧。
使用场景
// 业务代码 不满足直接抛出异常即可
if ("" = trim($username)) {
throw new BadRequestHttpException("用户名必须");
}
// 全局处理器
public function render($request, Exception $exception)
{
if ($exception instanceof BadRequestHttpException) {
return response()->json([
"err" => 400,
"msg" => $exception->getMessage()
]);
}
if ($exception instanceof AccessDeniedHttpException) {
return response()->json([
"err" => 403,
"msg" => "unauthorized"
]);
}
if ($exception instanceof NotFoundHttpException) {
return response()->json([
"err" => 403,
"msg" => "forbidden"
]);
}
if ($exception instanceof NotFoundHttpException) {
return response()->json([
"err" => 404,
"msg" => "not found"
]);
}
if ($exception instanceof MethodNotAllowedHttpException) {
return response()->json([
"err" => 405,
"msg" => "method not allowed"
]);
}
if ($exception instanceof MethodNotAllowedHttpException) {
return response()->json([
"err" => 406,
"msg" => "你想要的数据类型我特么给不了啊"
]);
}
if ($exception instanceof TooManyRequestsHttpException) {
return response()->json([
"err" => 429,
"msg" => "to many request"
]);
}
return parent::render($request, $exception);
}
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Vue 源码解析(实例化前) - 初始化全局API(二)
- Vue 源码解析(实例化前) - 初始化全局API(三)
- vue 源码解析(实例化前) - 初始化全局 API(最终章)
- 漫画人脸检测 | 全局和局部信息融合的深度神经网络(文末源码)
- 全局变量,静态全局变量,局部变量,静态局部变量
- Thrift RPC 系列教程(2)——全局变量&全局常量
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
阿里巴巴Java开发手册
杨冠宝 / 电子工业出版社 / 2018-1 / 35
《阿里巴巴Java开发手册》的愿景是码出高效,码出质量。它结合作者的开发经验和架构历程,提炼阿里巴巴集团技术团队的集体编程经验和软件设计智慧,浓缩成为立体的编程规范和最佳实践。众所周知,现代软件行业的高速发展对开发者的综合素质要求越来越高,因为不仅是编程相关的知识点,其他维度的知识点也会影响软件的最终交付质量,比如,数据库的表结构和索引设计缺陷可能带来软件的架构缺陷或性能风险;单元测试的失位导致集......一起来看看 《阿里巴巴Java开发手册》 这本书的介绍吧!