Listening to the Words

hyperf之AOP执行原理

AOP 面向横切片编程

hyperf官方文档的介绍如下:

AOPAspect Oriented Programming 的缩写,意为:面向切面编程,通过动态代理等技术实现程序功能的统一维护的一种技术。
AOP 是 OOP 的延续,也是 Hyperf 中的一个重要内容,是函数式编程的一种衍生范型。
利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP如何实现的呢?答案是通过动态代理实现。

举个例子:

在国内无法访问谷歌和facebook,可是又想访问怎么办? 这时就有了代理。

代理模式是面向对象23种设计模式的一种。简单来说就是在真实对象和访问者抽象出一层代理,并且还可以真实对象进行处理的一种技术。

想想你去买京东买手机,京东还会赠送你手机膜、或者小礼品等等,如果直接去厂商买。。。。大概也找不到地方在哪。而京东就是这个中间代理商。

上代码

class ProxyFactory
{
    /**
     * @var array
     */
    private static $map = [];

    /**
     * @var Ast
     */
    private $ast;

    public function __construct()
    {
        $this->ast = new Ast();
    }

    //创建AOP动态代理
    //ObjectDefinition:需要被代理的类
    public function createProxyDefinition(ObjectDefinition $definition): ObjectDefinition
    {

        $identifier = $definition->getName();
        if (isset(static::$map[$identifier])) {
            return static::$map[$identifier];
        }
        //对所有注解种使用了AOP的类、方法生成代理,次时已经判断好哪些是需要代理
        $proxyIdentifier = $definition->getClassName() . '_' . md5($definition->getClassName());
        //生成代理类名
        $definition->setProxyClassName($proxyIdentifier);
        //加载代理类
        $this->loadProxy($definition->getClassName(), $definition->getProxyClassName());

        static::$map[$identifier] = $definition;
        return static::$map[$identifier];
    }

    private function loadProxy(string $className, string $proxyClassName): void
    {
        $dir = BASE_PATH . '/runtime/container/proxy/';
        if (! file_exists($dir)) {
            mkdir($dir, 0755, true);
        }
        $proxyFileName = str_replace('\\', '_', $className);
        $path = $dir . $proxyFileName . '.proxy.php';

        $key = md5($path);
        // If the proxy file does not exist, then try to acquire the coroutine lock.
        if (! file_exists($path) && CoLocker::lock($key)) {
            $targetPath = $path . '.' . uniqid();
            $code = $this->ast->proxy($className, $proxyClassName);
            file_put_contents($targetPath, $code);
            rename($targetPath, $path);
            CoLocker::unlock($key);
        }
        include_once $path;
    }
}

通过这个代理工厂类,在启动阶段hyperf就生成了代理类,并且加载到了容器里:

/**
 * Class IndexController
 */
class IndexController_6e99c4e5b3e9af77517e08045e6e6836 extends IndexController
{
    use \Hyperf\Di\Aop\ProxyTrait;
    protected $app;
    protected $notify;
    public function __construct()
    {
            $this->app = ApplicationContext::getContainer()->get(ReportServiceContract::class);
    }
    /**
     * @Cacheable(prefix="kuang_",ttl=1000,collect=true)
     * @return int
     */
    public function index()
    {
        $__function__ = __FUNCTION__;
        $__method__ = __METHOD__;
        return self::__proxyCall(IndexController::class, __FUNCTION__, self::getParamsMap(IndexController::class, __FUNCTION__, func_get_args()), function () use($__function__, $__method__) {
            Log::info('rongqi', [ApplicationContext::getContainer()]);
            return 1;
        });
    }
}

这是最终形成的一个代理类,仔细看核心是 __proxyCall(),最后一个参数是闭包也就是执行的真实对象(类)。

//trait ProxyTrait
 protected static function __proxyCall(
        string $originalClassName,
        string $method,
        array $arguments,
        Closure $closure
    ) {
        $proceedingJoinPoint = new ProceedingJoinPoint($closure, $originalClassName, $method, $arguments);
        $result = self::handleAround($proceedingJoinPoint);
        unset($proceedingJoinPoint);
        return $result;
    }

接着通过ProceedingJoinPoint::class将包装产品,process方法就是被代理的真实对象,也就是闭包里执行的业务逻辑

紧接着就是AOP切面要执行的逻辑了,就像是京东买手机送贴膜或者充电宝之类的行为了。他完全不影响买手机本身的行为还有额外惊喜。

如何实现呢?答案是pipeline,什么是pipeline? laravel里大名鼎鼎的路由中间件实现的核心,本质是个洋葱模型。真的很复杂。

    private static function handleAround(ProceedingJoinPoint $proceedingJoinPoint)
    {
        $aspects = self::getAspects($proceedingJoinPoint->className, $proceedingJoinPoint->methodName);
        $annotationAspects = self::getAnnotationAspects($proceedingJoinPoint->className, $proceedingJoinPoint->methodName);
        $aspects = array_unique(array_merge($aspects, $annotationAspects));
        if (empty($aspects)) {
            return $proceedingJoinPoint->processOriginalMethod();
        }

        $container = ApplicationContext::getContainer();
        if (method_exists($container, 'make')) {
            $pipeline = $container->make(Pipeline::class);
        } else {
            $pipeline = new Pipeline($container);
        }
        //在这里
        return $pipeline->via('process')
            //aop的行为
            ->through($aspects)
            ->send($proceedingJoinPoint)
            //最终执行的核心京东买手机的行为
            ->then(function (ProceedingJoinPoint $proceedingJoinPoint) {
                return $proceedingJoinPoint->processOriginalMethod();
            });
    }
//这就是要执行的AOP切面方法。
public function process(ProceedingJoinPoint $proceedingJoinPoint)
    {
        //京东开始处理订单了
        $className = $proceedingJoinPoint->className;
        $method = $proceedingJoinPoint->methodName;
        $arguments = $proceedingJoinPoint->arguments['keys'];

        [$key, $ttl, $group, $annotation] = $this->annotationManager->getCacheableValue($className, $method, $arguments);

        $driver = $this->manager->getDriver($group);

        [$has, $result] = $driver->fetch($key);
        if ($has) {
            return $result;
        }

        //这是我们真实的核心,想买的手机。

        $result = $proceedingJoinPoint->process();

        //京东送的包装盒
        $driver->set($key, $result, $ttl);
        if ($driver instanceof KeyCollectorInterface && $annotation instanceof Cacheable && $annotation->collect) {
            $driver->addKey($annotation->prefix . 'MEMBERS', $key);
        }

        return $result;
    }

这么一系列流程下来就完成了整个AOP切面行为,是不是很复杂?

点赞