摘要:官方在文檔沒有提供完整的但我們還是可以在單元測試中找得到的用法。解決的問題是分散在引用各處的橫切關注點。橫切關注點指的是分布于應用中多處的功能,譬如日志,事務和安全。通過將真正執行操作的對象委托給實現了能提供許多功能。源碼剖析系列目錄
作者:bromine
鏈接:https://www.jianshu.com/p/e13...
來源:簡書
著作權歸作者所有,本文已獲得作者授權轉載,并對原文進行了重新的排版。
Swoft Github: https://github.com/swoft-clou...
AOP(面向切面編程)一方面是是開閉原則的良好實踐,你可以在不修改代碼的前提下為項目添加功能;更重要的是,在面向對象以外,他提供你另外一種思路去復用你的瑣碎代碼,并將其和你的業務代碼風格開。
初探AOPAOP是被Spring發揚光大的一個概念,在Java Web的圈子內可謂無人不曉,但是在PHP圈內其實現甚少,因此很多PHPer對相關概念很陌生。且Swoft文檔直接說了一大堆術語如AOP、切面、切面、通知、連接點、切入點,卻只給了一個關于Aspect(切面)的示例。沒有接觸過AOP的PHPer對于此肯定是一頭霧水的。考慮到這點我們先用一點小篇幅來談談相關知識,熟悉的朋友可以直接往后跳。
基于實踐驅動學習的理念,這里我們先不談概念,先幫官網把示例補全。官方在文檔沒有提供完整的AOP Demo,但我們還是可以在單元測試中找得到的用法。
這里是Aop的其中一個單元測試,這個測試的目的是檢查AopTest->doAop()的返回值是否是:
"do aop around-before2 before2 around-after2 afterReturn2 around-before1 before1 around-after1 afterReturn1 "
//SwoftTestCasesAopTest.php class AopTest extends TestCase { public function testAllAdvice() { /* @var SwoftTestingAopAopBean $aopBean*/ $aopBean = App::getBean(AopBean::class); $result = $aopBean->doAop(); //此處是PHPUnit的斷言語法,他判斷AopBean Bean的doAop()方法的返回值是否是符合預期 $this->assertEquals("do aop around-before2 before2 around-after2 afterReturn2 around-before1 before1 around-after1 afterReturn1 ", $result); } }
上面的測試使用到了AopBean::class這個Bean。這個bean有一個很簡單的方法doAop(),直接返回一串固定的字符串"do aop";
發現問題了沒?單元測試中$aopBean沒有顯式的使用編寫AOP相關代碼,而$aopBean->doAop()的返回值卻被改寫了。
這就是AOP的威力了,他可以以一種完全無感知無侵入的方式去拓展你的功能。但拓展代碼并不完全是AOP的目的,AOP的意義在于分離你的零碎關注點,以一種面向對象外的思路去組織和復用你的各種零散邏輯。AOP解決的問題是分散在引用各處的橫切關注點。橫切關注點指的是分布于應用中多處的功能,譬如日志,事務和安全。通常來說橫切關注點本身是和業務邏輯相分離的,但按照傳統的編程方式,橫切關注點只能零散的嵌入到各個邏輯代碼中。因此我們引入了AOP,他不僅提供一種集中式的方式去管理這些橫切關注點,而且分離了核心的業務代碼和橫切關注點,橫切關注點的修改不再需要修改核心代碼。
回到官方給的切面實例
test .= " before1 "; } //other code.... }上面的AllPointAspect主要使用了3個注解去描述一個切面(Aspect)
@Aspect聲明這是一個切面(Aspect)類,一組被組織起來的橫切關注點。
@Before聲明了一個通知(Advice)方法,即切面要干什么和什么時候執行
@PointBean聲明了一個切點(PointCut):即 切面(Aspect)在何處執行,通知(Advice)能匹配哪些連接點。關于AOP的更多知識可以閱讀
動態代理 代理模式代理模式(Proxy /Surrogate)是GOF系23種設計模式中的其中一種。其定義為:
為對象提供一個代理,以控制對這個對象的訪問。其常見實現的序列圖和類圖如下
序列圖
類圖RealSubject是真正執行操作的實體
Subject是從RealSubject中抽離出的抽象接口,用于屏蔽具體的實現類
Proxy是代理,實現了Subject接口,一般會持有一個RealSubjecy實例,將Client調用的方法委托給RealSubject真正執行。通過將真正執行操作的對象委托給實現了Proxy能提供許多功能。
遠程代理(Remote Proxy/Ambassador):為一個不同地址空間的實例提供本地環境的代理,隱藏遠程通信等復雜細節。
保護代理(Protection Proxy)對RealSubject的訪問提供權限控制等額外功能。
虛擬代理(Virtual Proxy)根據實際需要創建開銷大的對象
智能引用(Smart Reference)可以在訪問對象時添加一些附件操作。更多可閱讀《設計模式 可復用面向對象軟件的基礎》的第四章
動態代理一般而言我們使用的是靜態代理,即:在編譯期前通過手工或者自動化工具預先生成相關的代理類源碼。
Swoft中的AOP
這不僅大大的增加了開發成本和類的數量,而且缺少彈性。因此AOP一般使用的代理類都是在運行期動態生成的,也就是動態代理回到Swoft,之所以示例中$aopBean的doAop()能被拓展的原因就是App::getBean(AopBean::class)返回的并不是AopBean的真正實例,而是一個持有AopBean對象的動態代理。
Container->set()方法是App::getBean()底層實際創建bean的方法。//SwoftBeanContainer.php /** * 創建Bean * * @param string $name 名稱 * @param ObjectDefinition $objectDefinition bean定義 * @return object * @throws ReflectionException * @throws InvalidArgumentException */ private function set(string $name, ObjectDefinition $objectDefinition) { //低相關code... //注意此處,在返回前使用了一個Aop動態代理對象包裝并替換實際對象,所以我們拿到的Bean都是Proxy if (!$object instanceof AopInterface) { $object = $this->proxyBean($name, $className, $object);// } //低相關code .... return $object; }Container->proxyBean()的主要操作有兩個
調用對Bean的各個方法調用Aop->match();根據切面定義的切點獲取其合適的通知,并注冊到Aop->map中
//SwoftAopAop.php /** * Match aop * * @param string $beanName Bean name * @param string $class Class name * @param string $method Method name * @param array $annotations The annotations of method */ public function match(string $beanName, string $class, string $method, array $annotations) { foreach ($this->aspects as $aspectClass => $aspect) { if (! isset($aspect["point"]) || ! isset($aspect["advice"])) { continue; } //下面的代碼根據各個切面的@PointBean,@PointAnnotation,@PointExecution 進行連接點匹配 // Include $pointBeanInclude = $aspect["point"]["bean"]["include"] ?? []; $pointAnnotationInclude = $aspect["point"]["annotation"]["include"] ?? []; $pointExecutionInclude = $aspect["point"]["execution"]["include"] ?? []; // Exclude $pointBeanExclude = $aspect["point"]["bean"]["exclude"] ?? []; $pointAnnotationExclude = $aspect["point"]["annotation"]["exclude"] ?? []; $pointExecutionExclude = $aspect["point"]["execution"]["exclude"] ?? []; $includeMath = $this->matchBeanAndAnnotation([$beanName], $pointBeanInclude) || $this->matchBeanAndAnnotation($annotations, $pointAnnotationInclude) || $this->matchExecution($class, $method, $pointExecutionInclude); $excludeMath = $this->matchBeanAndAnnotation([$beanName], $pointBeanExclude) || $this->matchBeanAndAnnotation($annotations, $pointAnnotationExclude) || $this->matchExecution($class, $method, $pointExecutionExclude); if ($includeMath && ! $excludeMath) { //注冊該方法級別的連接點適配的各個通知 $this->map[$class][$method][] = $aspect["advice"]; } } }通過Proxy::newProxyInstance(get_class($object),new AopHandler($object))構造一個動態代理
//SwoftProxyProxy.php /** * return a proxy instance * * @param string $className * @param HandlerInterface $handler * * @return object */ public static function newProxyInstance(string $className, HandlerInterface $handler) { $reflectionClass = new ReflectionClass($className); $reflectionMethods = $reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED); // the template of methods $id = uniqid(); $proxyClassName = basename(str_replace("", "/", $className)); $proxyClassName = $proxyClassName . "_" . $id; //動態類直接繼承RealSubject $template = "class $proxyClassName extends $className { private $hanadler; public function __construct($handler) { $this->hanadler = $handler; } "; // the template of methods //proxy類會重寫所有非static非構造器函數,將實現改為調用給$handler的invoke()函數 $template .= self::getMethodsTemplate($reflectionMethods); $template .= "}"; //通過動態生成的源碼構造一個動態代理類,并通過反射獲取動態代理的實例 eval($template); $newRc = new ReflectionClass($proxyClassName); return $newRc->newInstance($handler); }構造動態代理需要一個SwoftProxyHandlerHandlerInterface實例作為$handler參數,AOP動態代理使用的是AopHandler,其invoke()底層的關鍵操作為Aop->doAdvice()
//SwoftAopAop.php /** * @param object $target Origin object * @param string $method The execution method * @param array $params The parameters of execution method * @param array $advices The advices of this object method * @return mixed * @throws ReflectionException|Throwable */ public function doAdvice($target, string $method, array $params, array $advices) { $result = null; $advice = array_shift($advices); try { // Around通知條用 if (isset($advice["around"]) && ! empty($advice["around"])) { $result = $this->doPoint($advice["around"], $target, $method, $params, $advice, $advices); } else { // Before if ($advice["before"] && ! empty($advice["before"])) { // The result of before point will not effect origin object method $this->doPoint($advice["before"], $target, $method, $params, $advice, $advices); } if (0 === count($advices)) { //委托請求給Realsuject $result = $target->$method(...$params); } else { //調用后續切面 $this->doAdvice($target, $method, $params, $advices); } } // After if (isset($advice["after"]) && ! empty($advice["after"])) { $this->doPoint($advice["after"], $target, $method, $params, $advice, $advices, $result); } } catch (Throwable $t) { if (isset($advice["afterThrowing"]) && ! empty($advice["afterThrowing"])) { return $this->doPoint($advice["afterThrowing"], $target, $method, $params, $advice, $advices, null, $t); } else { throw $t; } } // afterReturning if (isset($advice["afterReturning"]) && ! empty($advice["afterReturning"])) { return $this->doPoint($advice["afterReturning"], $target, $method, $params, $advice, $advices, $result); } return $result; }通知的執行(Aop->doPoint())也很簡單,構造ProceedingJoinPoint,JoinPoint,Throwable對象,并根據通知的參數聲明注入。
//SwoftAopAop.php /** * Do pointcut * * @param array $pointAdvice the pointcut advice * @param object $target Origin object * @param string $method The execution method * @param array $args The parameters of execution method * @param array $advice the advice of pointcut * @param array $advices The advices of this object method * @param mixed $return * @param Throwable $catch The Throwable object caught * @return mixed * @throws ReflectionException */ private function doPoint( array $pointAdvice, $target, string $method, array $args, array $advice, array $advices, $return = null, Throwable $catch = null ) { list($aspectClass, $aspectMethod) = $pointAdvice; $reflectionClass = new ReflectionClass($aspectClass); $reflectionMethod = $reflectionClass->getMethod($aspectMethod); $reflectionParameters = $reflectionMethod->getParameters(); // Bind the param of method $aspectArgs = []; foreach ($reflectionParameters as $reflectionParameter) { //用反射獲取參數類型,如果是JoinPoint,ProceedingJoinPoint,或特定Throwable,則注入,否則直接傳null $parameterType = $reflectionParameter->getType(); if ($parameterType === null) { $aspectArgs[] = null; continue; } // JoinPoint object $type = $parameterType->__toString(); if ($type === JoinPoint::class) { $aspectArgs[] = new JoinPoint($target, $method, $args, $return, $catch); continue; } // ProceedingJoinPoint object if ($type === ProceedingJoinPoint::class) { $aspectArgs[] = new ProceedingJoinPoint($target, $method, $args, $advice, $advices); continue; } //Throwable object if (isset($catch) && $catch instanceof $type) { $aspectArgs[] = $catch; continue; } $aspectArgs[] = null; } $aspect = ean($aspectClass); return $aspect->$aspectMethod(...$aspectArgs); }以上就是AOP的整體實現原理了。
Swoft源碼剖析系列目錄:https://segmentfault.com/a/11...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/30703.html
摘要:作者鏈接來源簡書著作權歸作者所有,本文已獲得作者授權轉載,并對原文進行了重新的排版。同時順手整理個人對源碼的相關理解,希望能夠稍微填補學習領域的空白。系列文章只會節選關鍵代碼輔以思路講解,請自行配合源碼閱讀。 作者:bromine鏈接:https://www.jianshu.com/p/2f6...來源:簡書著作權歸作者所有,本文已獲得作者授權轉載,并對原文進行了重新的排版。Swoft...
摘要:作者鏈接來源簡書著作權歸作者所有,本文已獲得作者授權轉載,并對原文進行了重新的排版。前言為應用提供一個完整的容器作為依賴管理方案,是功能,模塊等功能的實現基礎。的依賴注入管理方案基于服務定位器。源碼剖析系列目錄 作者:bromine鏈接:https://www.jianshu.com/p/a23...來源:簡書著作權歸作者所有,本文已獲得作者授權轉載,并對原文進行了重新的排版。Swof...
摘要:中的注解注解是里面很多重要功能特別是,容器的基礎。主流的框架中使用的注解都是借用型注釋塊型注釋中的定義自己的注解機制。在中是注解信息的最終裝載容器。使用的信息構造實例或獲取現有實例以上就是注解機制的整體實現了。源碼剖析系列目錄 作者:bromine鏈接:https://www.jianshu.com/p/ef7...來源:簡書著作權歸作者所有,本文已獲得作者授權轉載,并對原文進行了重新...
摘要:值得一提的是目前的服務即服務,暫沒有其他的服務功能,所以基本上相關的配置指代的就是。會將請求傳遞給各個中間件,最終最終傳遞給處理。源碼剖析系列目錄 作者:bromine鏈接:https://www.jianshu.com/p/411...來源:簡書著作權歸作者所有,本文已獲得作者授權轉載,并對原文進行了重新的排版。Swoft Github: https://github.com/swo...
摘要:和服務關系最密切的進程是中的進程組,絕大部分業務處理都在該進程中進行。隨后觸發一個事件各組件通過該事件進行配置文件加載路由注冊。事件每個請求到來時僅僅會觸發事件。服務器生命周期和服務基本一致,詳情參考源碼剖析功能實現 作者:bromine鏈接:https://www.jianshu.com/p/4c0...來源:簡書著作權歸作者所有,本文已獲得作者授權轉載,并對原文進行了重新的排版。S...
閱讀 2847·2021-09-10 10:51
閱讀 2215·2021-09-02 15:21
閱讀 3206·2019-08-30 15:44
閱讀 869·2019-08-29 18:34
閱讀 1652·2019-08-29 13:15
閱讀 3322·2019-08-26 11:37
閱讀 2697·2019-08-26 10:46
閱讀 1107·2019-08-26 10:26