做者:bromine
連接:https://www.jianshu.com/p/e13...
來源:簡書
著做權歸做者全部,本文已得到做者受權轉載,並對原文進行了從新的排版。
Swoft Github: https://github.com/swoft-clou...php
AOP(面向切面編程)一方面是是開閉原則的良好實踐,你能夠在不修改代碼的前提下爲項目添加功能;更重要的是,在面向對象之外,他提供你另一種思路去複用你的瑣碎代碼,並將其和你的業務代碼風格開。git
AOP是被Spring發揚光大的一個概念,在Java Web的圈子內可謂無人不曉,可是在PHP圈內其實現甚少,所以不少PHPer對相關概念很陌生。且Swoft文檔直接說了一大堆術語如AOP、切面、切面、通知、鏈接點、切入點,卻只給了一個關於Aspect(切面)的示例。沒有接觸過AOP的PHPer對於此確定是一頭霧水的。考慮到這點咱們先用一點小篇幅來談談相關知識,熟悉的朋友能夠直接日後跳。github
基於實踐驅動學習的理念,這裏咱們先不談概念,先幫官網把示例補全。官方在文檔沒有提供完整的AOP Demo,但咱們仍是能夠在單元測試中找獲得的用法。編程
這裏是Aop的其中一個單元測試,這個測試的目的是檢查AopTest->doAop()
的返回值是不是:'do aop around-before2 before2 around-after2 afterReturn2 around-before1 before1 around-after1 afterReturn1 '
segmentfault
//Swoft\Test\Cases\AopTest.php class AopTest extends TestCase { public function testAllAdvice() { /* @var \Swoft\Testing\Aop\AopBean $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";設計模式
<?php //Swoft\Test\Testing\Aop\AopBean.php /** * @Bean() */ class AopBean { public function doAop() { return "do aop"; } }
發現問題了沒?單元測試中$aopBean
沒有顯式的使用編寫AOP相關代碼,而$aopBean->doAop()
的返回值卻被改寫了。
這就是AOP的威力了,他能夠以一種徹底無感知無侵入的方式去拓展你的功能。但拓展代碼並不徹底是AOP的目的,AOP的意義在於分離你的零碎關注點,以一種面向對象外的思路去組織和複用你的各類零散邏輯。安全
AOP
解決的問題是分散在引用各處的橫切關注點
。橫切關注點
指的是分佈於應用中多處的功能,譬如日誌,事務和安全。一般來講橫切關注點自己是和業務邏輯相分離的,但按照傳統的編程方式,橫切關注點只能零散的嵌入到各個邏輯代碼中。所以咱們引入了AOP,他不只提供一種集中式的方式去管理這些橫切關注點,並且分離了核心的業務代碼和橫切關注點,橫切關注點的修改再也不須要修改核心代碼。函數
回到官方給的切面實例工具
<?php //Swoft\Test\Testing\Aop\AllPointAspect.php /** * @Aspect() * @PointBean( * include={AopBean::class}, * )(Joinpoint) */ class AllPointAspect { //other code.... /** * @Before() */ public function before() { $this->test .= ' before1 '; } //other code.... }
上面的AllPointAspect
主要使用了3個註解去描述一個切面(Aspect)
@Aspect聲明這是一個切面(Aspect)類,一組被組織起來的橫切關注點。
@Before聲明瞭一個通知(Advice)方法,即切面要幹什麼和何時執行
@PointBean聲明瞭一個切點(PointCut):即 切面(Aspect)在何處執行,通知(Advice)能匹配哪些鏈接點。單元測試
關於AOP的更多知識能夠閱讀<Spring實戰>
代理模式(Proxy /Surrogate)是GOF系23種設計模式中的其中一種。其定義爲:
爲對象提供一個代理,以控制對這個對象的訪問。
其常見實現的序列圖和類圖以下
序列圖
類圖
RealSubject是真正執行操做的實體
Subject是從RealSubject中抽離出的抽象接口,用於屏蔽具體的實現類
Proxy是代理,實現了Subject接口,通常會持有一個RealSubjecy實例,將Client調用的方法委託給RealSubject真正執行。
經過將真正執行操做的對象委託給實現了Proxy能提供許多功能。
遠程代理(Remote Proxy/Ambassador):爲一個不一樣地址空間的實例提供本地環境的代理,隱藏遠程通訊等複雜細節。
保護代理(Protection Proxy)對RealSubject的訪問提供權限控制等額外功能。
虛擬代理(Virtual Proxy)根據實際須要建立開銷大的對象
智能引用(Smart Reference)能夠在訪問對象時添加一些附件操做。
通常而言咱們使用的是靜態代理,即:在編譯期前經過手工或者自動化工具預先生成相關的代理類源碼。
這不只大大的增長了開發成本和類的數量,並且缺乏彈性。所以AOP通常使用的代理類都是在運行期動態生成的,也就是動態代理
回到Swoft,之因此示例中$aopBean
的doAop()
能被拓展的緣由就是App::getBean(AopBean::class)
返回的並非AopBean的真正實例,而是一個持有AopBean對象的動態代理
。Container->set()
方法是App::getBean()
底層實際建立bean的方法。
//Swoft\Bean\Container.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()
的主要操做有兩個
//Swoft\Aop\Aop.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']; } } }
//Swoft\Proxy\Proxy.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); }
構造動態代理須要一個Swoft\Proxy\Handler\HandlerInterface
實例做爲$handler
參數,AOP動態代理使用的是AopHandler
,其invoke()
底層的關鍵操做爲Aop->doAdvice()
//Swoft\Aop\Aop.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對象,並根據通知的參數聲明注入。
//Swoft\Aop\Aop.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 = \bean($aspectClass); return $aspect->$aspectMethod(...$aspectArgs); }
以上就是AOP的總體實現原理了。
Swoft源碼剖析系列目錄: https://segmentfault.com/a/11...