做者: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...