Aop的出現是為了解耦業務程式碼,它的理念與我們之前接觸到的如中介軟體(MiddleWare)、事件機制的作用其實是差不多的,都是一種解耦操作。但是在實現上有很大的差別。
MiddleWare使用場景最常見的如介面鑑權、引數校驗等,事件就是通過監聽、觸發操作,如支付成功的簡訊通知等。
Aop簡介Aop是一種程式設計思想或者模型,通過預編譯及代理類實現對原有業務的擴充套件與增強,而對於原始碼來說是無感的。簡單來說我們的了程式碼A,經過Aop處理之後生成了S-A-B,S-B就是Aop的功能的提現。
我的理解就是在不改變原始碼的情況下,改變程式碼的執行邏輯,因為它是通過代理實現,至於什麼時候切、切完要幹啥還是需要我們自己去實現。
程式碼:
TestAspect.php
<?phpdeclare(strict_types=1);namespace App\\Aspect;use App\\Service\\LogService;use Hyperf\\Di\\Annotation\\Aspect;use Hyperf\\Di\\Aop\\AbstractAspect;use Hyperf\\Di\\Aop\\ProceedingJoinPoint;/** * Class TestAspect * @package App\\Aspect * * // 宣告切面 * @Aspect( // 在哪些方法進行切入 * classes={ * "App\\Service\\LoginService::login", * "App\\Controller\\AopTestController::login", * * },\t priority=2 // 優先順序-可以設定多個奧 * * ) */class TestAspect extends AbstractAspect{ protected $logService; public function __construct(LogService $logService) { $this->logService = $logService; } public function process(ProceedingJoinPoint $proceedingJoinPoint) { // 執行之前進行通知 $this->logService->log('get-request to '. $proceedingJoinPoint->className. '::'. $proceedingJoinPoint->methodName); // 執行切點方法 也就是上面宣告的login方法 $result = $proceedingJoinPoint->process(); // 執行之後通知 $this->logService->log('get-result from '. $proceedingJoinPoint->className. '::'. $proceedingJoinPoint->methodName, $result); return $result; }}
AopTestController.php
<?php/** * @GetMapping("login") * * @param RequestInterface $request * @param ResponseInterface $response * @return mixed */ public function login(RequestInterface $request, ResponseInterface $response) { $userName = $request->input('userName'); return $this->loginService->login($userName); }
啟動服務之後,我們訪問127.0.0.1:9501/aop/login可以看到列印的日誌 runtime/logs/hyperf.log
[2020-09-03 16:33:58] hyperf.INFO: get-request-2 to App\\Controller\\AopTestController::login [] [][2020-09-03 16:33:58] hyperf.INFO: get-request to App\\Controller\\AopTestController::login [] [][2020-09-03 16:33:58] hyperf.INFO: get-request-2 to App\\Service\\LoginService::login [] [][2020-09-03 16:33:58] hyperf.INFO: get-request to App\\Service\\LoginService::login [] [][2020-09-03 16:33:58] hyperf.INFO: get-result from App\\Service\\LoginService::login {"user":"liu","time":"2020-09-03 16:33:58"} [][2020-09-03 16:33:58] hyperf.INFO: get-result-2 from App\\Service\\LoginService::login {"user":"liu","time":"2020-09-03 16:33:58"} [][2020-09-03 16:33:58] hyperf.INFO: get-result from App\\Controller\\AopTestController::login {"user":"liu","time":"2020-09-03 16:33:58"} [][2020-09-03 16:33:58] hyperf.INFO: get-result-2 from App\\Controller\\AopTestController::login {"user":"liu","time":"2020-09-03 16:33:58"} []
如何實現我們說Aop通過宣告切面、切點最後通過代理類實現增強功能。具體來看程式碼。Hyperf在runtime/container/proxy/ 下生成切點類的代理類
App_Controller_AopTestController.proxy.php
<?php/** * @GetMapping("login") * * @param RequestInterface $request * @param ResponseInterface $response * @return mixed */ public function login(RequestInterface $request, ResponseInterface $response) { $__function__ = __FUNCTION__; $__method__ = __METHOD__; // 傳入(當前類,當前方法,引數對映,一個匿名函式包裝的當前邏輯) return self::__proxyCall(__CLASS__, __FUNCTION__, self::__getParamsMap(__CLASS__, __FUNCTION__, func_get_args()), function (RequestInterface $request, ResponseInterface $response) use($__function__, $__method__) { $userName = $request->input('userName'); return $this->loginService->login($userName); }); }
可以看到這個檔案跟我們的原始碼有點不一樣,使用一個__proxyCall方法將我們的原邏輯進行了包裝。在實際執行的時候就是執行了這個代理類。
我們來看這個proxyCall幹啥了
<?phpprotected static function __proxyCall( string $className, string $method, array $arguments, Closure $closure ) {\t\t// 把傳入的類包裝為一個切入點 $proceedingJoinPoint = new ProceedingJoinPoint($closure, $className, $method, $arguments); $result = self::handleAround($proceedingJoinPoint); unset($proceedingJoinPoint); return $result; } /** * @TODO This method will be called everytime, should optimize it later. */ protected static function __getParamsMap(string $className, string $method, array $args): array { $map = [ 'keys' => [], 'order' => [], ]; $reflectMethod = ReflectionManager::reflectMethod($className, $method); $reflectParameters = $reflectMethod->getParameters(); $leftArgCount = count($args); foreach ($reflectParameters as $key => $reflectionParameter) { $arg = $reflectionParameter->isVariadic() ? $args : array_shift($args); if (! isset($arg) && $leftArgCount <= 0) { $arg = $reflectionParameter->getDefaultValue(); } --$leftArgCount; $map['keys'][$reflectionParameter->getName()] = $arg; $map['order'][] = $reflectionParameter->getName(); } return $map; } protected static function handleAround(ProceedingJoinPoint $proceedingJoinPoint) { // 類的名稱 $className = $proceedingJoinPoint->className; // 方法名 $methodName = $proceedingJoinPoint->methodName; if (! AspectManager::has($className, $methodName)) { AspectManager::set($className, $methodName, []); // 獲取這個類的所有的切面類 就是@Aspect 中包含當前這個類的 $aspects = array_unique(array_merge(static::getClassesAspects($className, $methodName), static::getAnnotationAspects($className, $methodName))); // 放入優先順序佇列 可以在@Aspect中設定priority屬性 $queue = new \\SplPriorityQueue(); foreach ($aspects as $aspect) { $queue->insert($aspect, AspectCollector::getPriority($aspect)); } while ($queue->valid()) { AspectManager::insert($className, $methodName, $queue->current()); $queue->next(); } unset($annotationAspects, $aspects, $queue); } if (empty(AspectManager::get($className, $methodName))) { return $proceedingJoinPoint->processOriginalMethod(); }\t\t// 這個是最終的執行流程 // 有沒有眼熟,是的 Laravel中我們見到過 // 這個是Pipe流水線的執行邏輯 // via()設定Aspect要執行的方法即process() return static::makePipeline()->via('process') // 設定當前類的切面 ->through(AspectManager::get($className, $methodName)) // 設定當前類為切點 ->send($proceedingJoinPoint) // 開始執行,then的核心就是array_reduce的執行,將陣列進行迴圈執行, 也就是一次一次的呼叫 // 這些Aspect的process方法 跟MiddleWare是一個思想,可以設定多個MiddleWare嘛 ->then(function (ProceedingJoinPoint $proceedingJoinPoint) { return $proceedingJoinPoint->processOriginalMethod(); }); }public function then(Closure $destination) { // 對pipes也就是Aspecs進行迴圈分別執行其process方法 // 最後執行destination也就是 我們原程式中的login的邏輯 $pipeline = array_reduce(array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)); return $pipeline($this->passable); }
總結:
這個Aop就這樣用了,Hyperf屬於精簡版的,因為對於通知包括前置通知、後置通知、環繞通知等,我們這裡是環繞通知,至於說應用場景有多少,目前我還沒有機會使用,大家可以想想實際的業務。
/file/2020/09/04/20200904094031_19.jpg.html
這篇文件講的比較詳細ojbk,大家可以看下學習學習。
詳細程式碼:https://github.com/nobody05/hyperf_study