AOP 面向横切片编程
hyperf官方文档的介绍如下:
AOP
为 Aspect 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切面行为,是不是很复杂?