摘要:官方在文檔沒(méi)有提供完整的但我們還是可以在單元測(cè)試中找得到的用法。解決的問(wèn)題是分散在引用各處的橫切關(guān)注點(diǎn)。橫切關(guān)注點(diǎn)指的是分布于應(yīng)用中多處的功能,譬如日志,事務(wù)和安全。通過(guò)將真正執(zhí)行操作的對(duì)象委托給實(shí)現(xiàn)了能提供許多功能。源碼剖析系列目錄
作者:bromine
鏈接:https://www.jianshu.com/p/e13...
來(lái)源:簡(jiǎn)書(shū)
著作權(quán)歸作者所有,本文已獲得作者授權(quán)轉(zhuǎn)載,并對(duì)原文進(jìn)行了重新的排版。
Swoft Github: https://github.com/swoft-clou...
AOP(面向切面編程)一方面是是開(kāi)閉原則的良好實(shí)踐,你可以在不修改代碼的前提下為項(xiàng)目添加功能;更重要的是,在面向?qū)ο笠酝猓峁┠懔硗庖环N思路去復(fù)用你的瑣碎代碼,并將其和你的業(yè)務(wù)代碼風(fēng)格開(kāi)。
初探AOPAOP是被Spring發(fā)揚(yáng)光大的一個(gè)概念,在Java Web的圈子內(nèi)可謂無(wú)人不曉,但是在PHP圈內(nèi)其實(shí)現(xiàn)甚少,因此很多PHPer對(duì)相關(guān)概念很陌生。且Swoft文檔直接說(shuō)了一大堆術(shù)語(yǔ)如AOP、切面、切面、通知、連接點(diǎn)、切入點(diǎn),卻只給了一個(gè)關(guān)于Aspect(切面)的示例。沒(méi)有接觸過(guò)AOP的PHPer對(duì)于此肯定是一頭霧水的??紤]到這點(diǎn)我們先用一點(diǎn)小篇幅來(lái)談?wù)勏嚓P(guān)知識(shí),熟悉的朋友可以直接往后跳。
基于實(shí)踐驅(qū)動(dòng)學(xué)習(xí)的理念,這里我們先不談概念,先幫官網(wǎng)把示例補(bǔ)全。官方在文檔沒(méi)有提供完整的AOP Demo,但我們還是可以在單元測(cè)試中找得到的用法。
這里是Aop的其中一個(gè)單元測(cè)試,這個(gè)測(cè)試的目的是檢查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的斷言語(yǔ)法,他判斷AopBean Bean的doAop()方法的返回值是否是符合預(yù)期 $this->assertEquals("do aop around-before2 before2 around-after2 afterReturn2 around-before1 before1 around-after1 afterReturn1 ", $result); } }
上面的測(cè)試使用到了AopBean::class這個(gè)Bean。這個(gè)bean有一個(gè)很簡(jiǎn)單的方法doAop(),直接返回一串固定的字符串"do aop";
發(fā)現(xiàn)問(wèn)題了沒(méi)?單元測(cè)試中$aopBean沒(méi)有顯式的使用編寫(xiě)AOP相關(guān)代碼,而$aopBean->doAop()的返回值卻被改寫(xiě)了。
這就是AOP的威力了,他可以以一種完全無(wú)感知無(wú)侵入的方式去拓展你的功能。但拓展代碼并不完全是AOP的目的,AOP的意義在于分離你的零碎關(guān)注點(diǎn),以一種面向?qū)ο笸獾乃悸啡ソM織和復(fù)用你的各種零散邏輯。AOP解決的問(wèn)題是分散在引用各處的橫切關(guān)注點(diǎn)。橫切關(guān)注點(diǎn)指的是分布于應(yīng)用中多處的功能,譬如日志,事務(wù)和安全。通常來(lái)說(shuō)橫切關(guān)注點(diǎn)本身是和業(yè)務(wù)邏輯相分離的,但按照傳統(tǒng)的編程方式,橫切關(guān)注點(diǎn)只能零散的嵌入到各個(gè)邏輯代碼中。因此我們引入了AOP,他不僅提供一種集中式的方式去管理這些橫切關(guān)注點(diǎn),而且分離了核心的業(yè)務(wù)代碼和橫切關(guān)注點(diǎn),橫切關(guān)注點(diǎn)的修改不再需要修改核心代碼。
回到官方給的切面實(shí)例
test .= " before1 "; } //other code.... }上面的AllPointAspect主要使用了3個(gè)注解去描述一個(gè)切面(Aspect)
@Aspect聲明這是一個(gè)切面(Aspect)類(lèi),一組被組織起來(lái)的橫切關(guān)注點(diǎn)。
@Before聲明了一個(gè)通知(Advice)方法,即切面要干什么和什么時(shí)候執(zhí)行
@PointBean聲明了一個(gè)切點(diǎn)(PointCut):即 切面(Aspect)在何處執(zhí)行,通知(Advice)能匹配哪些連接點(diǎn)。關(guān)于AOP的更多知識(shí)可以閱讀
動(dòng)態(tài)代理 代理模式代理模式(Proxy /Surrogate)是GOF系23種設(shè)計(jì)模式中的其中一種。其定義為:
為對(duì)象提供一個(gè)代理,以控制對(duì)這個(gè)對(duì)象的訪問(wèn)。其常見(jiàn)實(shí)現(xiàn)的序列圖和類(lèi)圖如下
序列圖
類(lèi)圖RealSubject是真正執(zhí)行操作的實(shí)體
Subject是從RealSubject中抽離出的抽象接口,用于屏蔽具體的實(shí)現(xiàn)類(lèi)
Proxy是代理,實(shí)現(xiàn)了Subject接口,一般會(huì)持有一個(gè)RealSubjecy實(shí)例,將Client調(diào)用的方法委托給RealSubject真正執(zhí)行。通過(guò)將真正執(zhí)行操作的對(duì)象委托給實(shí)現(xiàn)了Proxy能提供許多功能。
遠(yuǎn)程代理(Remote Proxy/Ambassador):為一個(gè)不同地址空間的實(shí)例提供本地環(huán)境的代理,隱藏遠(yuǎn)程通信等復(fù)雜細(xì)節(jié)。
保護(hù)代理(Protection Proxy)對(duì)RealSubject的訪問(wèn)提供權(quán)限控制等額外功能。
虛擬代理(Virtual Proxy)根據(jù)實(shí)際需要?jiǎng)?chuàng)建開(kāi)銷(xiāo)大的對(duì)象
智能引用(Smart Reference)可以在訪問(wèn)對(duì)象時(shí)添加一些附件操作。更多可閱讀《設(shè)計(jì)模式 可復(fù)用面向?qū)ο筌浖幕A(chǔ)》的第四章
動(dòng)態(tài)代理一般而言我們使用的是靜態(tài)代理,即:在編譯期前通過(guò)手工或者自動(dòng)化工具預(yù)先生成相關(guān)的代理類(lèi)源碼。
Swoft中的AOP
這不僅大大的增加了開(kāi)發(fā)成本和類(lèi)的數(shù)量,而且缺少?gòu)椥?。因此AOP一般使用的代理類(lèi)都是在運(yùn)行期動(dòng)態(tài)生成的,也就是動(dòng)態(tài)代理回到Swoft,之所以示例中$aopBean的doAop()能被拓展的原因就是App::getBean(AopBean::class)返回的并不是AopBean的真正實(shí)例,而是一個(gè)持有AopBean對(duì)象的動(dòng)態(tài)代理。
Container->set()方法是App::getBean()底層實(shí)際創(chuàng)建bean的方法。//SwoftBeanContainer.php /** * 創(chuàng)建Bean * * @param string $name 名稱(chēng) * @param ObjectDefinition $objectDefinition bean定義 * @return object * @throws ReflectionException * @throws InvalidArgumentException */ private function set(string $name, ObjectDefinition $objectDefinition) { //低相關(guān)code... //注意此處,在返回前使用了一個(gè)Aop動(dòng)態(tài)代理對(duì)象包裝并替換實(shí)際對(duì)象,所以我們拿到的Bean都是Proxy if (!$object instanceof AopInterface) { $object = $this->proxyBean($name, $className, $object);// } //低相關(guān)code .... return $object; }Container->proxyBean()的主要操作有兩個(gè)
調(diào)用對(duì)Bean的各個(gè)方法調(diào)用Aop->match();根據(jù)切面定義的切點(diǎn)獲取其合適的通知,并注冊(cè)到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; } //下面的代碼根據(jù)各個(gè)切面的@PointBean,@PointAnnotation,@PointExecution 進(jìn)行連接點(diǎn)匹配 // 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) { //注冊(cè)該方法級(jí)別的連接點(diǎn)適配的各個(gè)通知 $this->map[$class][$method][] = $aspect["advice"]; } } }通過(guò)Proxy::newProxyInstance(get_class($object),new AopHandler($object))構(gòu)造一個(gè)動(dòng)態(tài)代理
//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; //動(dòng)態(tài)類(lèi)直接繼承RealSubject $template = "class $proxyClassName extends $className { private $hanadler; public function __construct($handler) { $this->hanadler = $handler; } "; // the template of methods //proxy類(lèi)會(huì)重寫(xiě)所有非static非構(gòu)造器函數(shù),將實(shí)現(xiàn)改為調(diào)用給$handler的invoke()函數(shù) $template .= self::getMethodsTemplate($reflectionMethods); $template .= "}"; //通過(guò)動(dòng)態(tài)生成的源碼構(gòu)造一個(gè)動(dòng)態(tài)代理類(lèi),并通過(guò)反射獲取動(dòng)態(tài)代理的實(shí)例 eval($template); $newRc = new ReflectionClass($proxyClassName); return $newRc->newInstance($handler); }構(gòu)造動(dòng)態(tài)代理需要一個(gè)SwoftProxyHandlerHandlerInterface實(shí)例作為$handler參數(shù),AOP動(dòng)態(tài)代理使用的是AopHandler,其invoke()底層的關(guān)鍵操作為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)) { //委托請(qǐng)求給Realsuject $result = $target->$method(...$params); } else { //調(diào)用后續(xù)切面 $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; }通知的執(zhí)行(Aop->doPoint())也很簡(jiǎn)單,構(gòu)造ProceedingJoinPoint,JoinPoint,Throwable對(duì)象,并根據(jù)通知的參數(shù)聲明注入。
//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) { //用反射獲取參數(shù)類(lèi)型,如果是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的整體實(shí)現(xiàn)原理了。
Swoft源碼剖析系列目錄:https://segmentfault.com/a/11...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/30703.html
摘要:作者鏈接來(lái)源簡(jiǎn)書(shū)著作權(quán)歸作者所有,本文已獲得作者授權(quán)轉(zhuǎn)載,并對(duì)原文進(jìn)行了重新的排版。同時(shí)順手整理個(gè)人對(duì)源碼的相關(guān)理解,希望能夠稍微填補(bǔ)學(xué)習(xí)領(lǐng)域的空白。系列文章只會(huì)節(jié)選關(guān)鍵代碼輔以思路講解,請(qǐng)自行配合源碼閱讀。 作者:bromine鏈接:https://www.jianshu.com/p/2f6...來(lái)源:簡(jiǎn)書(shū)著作權(quán)歸作者所有,本文已獲得作者授權(quán)轉(zhuǎn)載,并對(duì)原文進(jìn)行了重新的排版。Swoft...
摘要:作者鏈接來(lái)源簡(jiǎn)書(shū)著作權(quán)歸作者所有,本文已獲得作者授權(quán)轉(zhuǎn)載,并對(duì)原文進(jìn)行了重新的排版。前言為應(yīng)用提供一個(gè)完整的容器作為依賴(lài)管理方案,是功能,模塊等功能的實(shí)現(xiàn)基礎(chǔ)。的依賴(lài)注入管理方案基于服務(wù)定位器。源碼剖析系列目錄 作者:bromine鏈接:https://www.jianshu.com/p/a23...來(lái)源:簡(jiǎn)書(shū)著作權(quán)歸作者所有,本文已獲得作者授權(quán)轉(zhuǎn)載,并對(duì)原文進(jìn)行了重新的排版。Swof...
摘要:中的注解注解是里面很多重要功能特別是,容器的基礎(chǔ)。主流的框架中使用的注解都是借用型注釋塊型注釋中的定義自己的注解機(jī)制。在中是注解信息的最終裝載容器。使用的信息構(gòu)造實(shí)例或獲取現(xiàn)有實(shí)例以上就是注解機(jī)制的整體實(shí)現(xiàn)了。源碼剖析系列目錄 作者:bromine鏈接:https://www.jianshu.com/p/ef7...來(lái)源:簡(jiǎn)書(shū)著作權(quán)歸作者所有,本文已獲得作者授權(quán)轉(zhuǎn)載,并對(duì)原文進(jìn)行了重新...
摘要:值得一提的是目前的服務(wù)即服務(wù),暫沒(méi)有其他的服務(wù)功能,所以基本上相關(guān)的配置指代的就是。會(huì)將請(qǐng)求傳遞給各個(gè)中間件,最終最終傳遞給處理。源碼剖析系列目錄 作者:bromine鏈接:https://www.jianshu.com/p/411...來(lái)源:簡(jiǎn)書(shū)著作權(quán)歸作者所有,本文已獲得作者授權(quán)轉(zhuǎn)載,并對(duì)原文進(jìn)行了重新的排版。Swoft Github: https://github.com/swo...
摘要:和服務(wù)關(guān)系最密切的進(jìn)程是中的進(jìn)程組,絕大部分業(yè)務(wù)處理都在該進(jìn)程中進(jìn)行。隨后觸發(fā)一個(gè)事件各組件通過(guò)該事件進(jìn)行配置文件加載路由注冊(cè)。事件每個(gè)請(qǐng)求到來(lái)時(shí)僅僅會(huì)觸發(fā)事件。服務(wù)器生命周期和服務(wù)基本一致,詳情參考源碼剖析功能實(shí)現(xiàn) 作者:bromine鏈接:https://www.jianshu.com/p/4c0...來(lái)源:簡(jiǎn)書(shū)著作權(quán)歸作者所有,本文已獲得作者授權(quán)轉(zhuǎn)載,并對(duì)原文進(jìn)行了重新的排版。S...
閱讀 2961·2021-09-10 10:51
閱讀 2293·2021-09-02 15:21
閱讀 3279·2019-08-30 15:44
閱讀 957·2019-08-29 18:34
閱讀 1732·2019-08-29 13:15
閱讀 3390·2019-08-26 11:37
閱讀 2761·2019-08-26 10:46
閱讀 1169·2019-08-26 10:26