热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

简述Yii2里的AOP思想

AOP是什么在软件业,AOP为AspectOrientedProgramming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

AOP是什么
在软件业,AOP为Aspect Oriented Programming的缩写,意为: 面向切面编程 ,通过 预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP解决什么
将日志记录,性能统计,安全控制,事务处理, 异常处理 等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
我怎么看AOP
从上述定义中看,AOP是将非业务行为从业务代码中抽离出来,再利用某种技术手段将它们切入回业务逻辑中去,这样做就可以灵活的改变这些行为实现而不需要改动业务代码了。
提取公共代码我们都会做,但是怎么切回到业务代码中呢?这就是AOP实现决定的了。上述定义中,其实AOP技术一般通过预编译或者运行期动态代理的方法实现,因此我就谈谈运行期动态代理的实现原理,以便对AOP思想做一个了解。
AOP实现 – 运行期动态代理
一般AOP框架都是给业务代码提供一个前置和后置的切入调用点,以此方式实现非业务行为的切入。
因此,动态代理的方式也不难理解,这里假设你已经有一个业务函数,希望在函数前后各插入一个调用点以实现切面编程,那么不侵入的方案就是提供另外一个函数来封装它,大概这样:


// 已有业务函数
function businessFunc() {}

// 代理函数
function proxyFunc(doBeforeFunc, doAfterFunc) {
// 前置切入点
call_user_func(doBeforeFunc);
businessFunc();
// 后置切入点
call_user_func(doAfterFunc);
}

// 调用代理
proxyFunc(function(){echo "before";}, function(){echo "end";});

可见,虽然对businessFunc没有做修改,但是调用方式需要改为proxyFunc,因此运行期动态代理的方案其实并不方便,这也是为什么AOP通常都在预编译阶段实现,因为运行期实现在一定程度上会影响代码的本质面貌的,关于PHP编译阶段实现AOP的库可以 看这个 。
Yii2的折衷方案
各种编译阶段的AOP实现,一般支持前置,后置,包围 这3类切入方式,一般以单个函数作为包装的单元,这个可以 看一下这个 ,这里不赘述。顺便一提,如果你对python的注解@了解的话,它就是这里提到的包围切入。
编译阶段AOP可以把埋点的过程隐藏起来,因此不需要对原有代码做任何修改。如果在运行期实现AOP,我们必须有所折衷,主要体现在:
● 显式的埋点,也就是通过在业务代码中,硬编码埋下AOP的调用点,优点是业务可以按需灵活埋点。
● 切面配置化生成,这样可以实现切面的灵活配置,仅需要修改配置而不需要修改业务代码。
在Yii2框架里,通过DI容器可以实现切面可配置,切面本身则是通过behavior行为模式和event事件机制彼此配合实现的,这样的实现符合AOP思想,可以解决切面类需求,又不依赖外部扩展,下面跟我来一探究竟。
event事件
切面的调用触发是投递一个event实现的,它记录了事件的类型,事件的内容,事件的触发者,下面是Event类:

class Event extends Object
{
/**
* @var string the event name. This property is set by [[Component::trigger()]] and [[trigger()]].
* Event handlers may use this property to check what event it is handling.
*/

public $name;
/**
* @var object the sender of this event. If not set, this property will be
* set as the object whose "trigger()" method is called.
* This property may also be a `null` when this event is a
* class-level event which is triggered in a static context.
*/

public $sender;
/**
* @var boolean whether the event is handled. Defaults to false.
* When a handler sets this to be true, the event processing will stop and
* ignore the rest of the uninvoked event handlers.
*/

public $handled = false;
/**
* @var mixed the data that is passed to [[Component::on()]] when attaching an event handler.
* Note that this varies according to which event handler is currently executing.
*/

public $data;

事件的触发者需要通过埋点的形式,将event投递出去,所有需要使用AOP的类都应该继承component类,它提供的trigger方法用来埋点:


public function trigger($name, Event $event = null)
{

$this->ensureBehaviors();
if (!empty($this->_events[$name])) {
if ($event === null) {
$event = new Event;
}
if ($event->sender === null) {
$event->sender = $this;
}
$event->handled = false;
$event->name = $name;
foreach ($this->_events[$name] as $handler) {
$event->data = $handler[1];
call_user_func($handler[0], $event);
// stop further handling if the event is handled
if ($event->handled) {
return;
}
}
}
// invoke class-level attached handlers
Event::trigger($this, $name, $event);
}

$name是事件的类型,当然也可以显式的传入一个event对象,事件被依次投递给_events[$name]中的处理函数进行处理,一旦某个处理函数标记事件已处理完成,调用链将终止。同时需要注意,event的sender属性表明了事件来源,因此事件处理函数可以访问sender中的一些方法来通知sender某些处理结论。
那么,问题其实就是事件处理函数是如何配置到component对象中的呢?我之前说过,这是通过DI将配置文件中的信息注入到属性中实现的。component类本身符合DI注入规范,可以用于从配置中注入属性,这是因为它实现了__set方法供DI容器注入:


public function __set($name, $value)
{

$setter = 'set' . $name;
if (method_exists($this, $setter)) {
// set property
$this->$setter($value);

return;
} elseif (strncmp($name, 'on ', 3) === 0) {
// on event: attach event handler
$this->on(trim(substr($name, 3)), $value);

return;
} elseif (strncmp($name, 'as ', 3) === 0) {
// as behavior: attach behavior
$name = trim(substr($name, 3));
$this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value));

return;
} else {
// behavior property
$this->ensureBehaviors();
foreach ($this->_behaviorsas $behavior) {
if ($behavior->canSetProperty($name)) {
$behavior->$name = $value;

return;
}
}
}
if (method_exists($this, 'get' . $name)) {
throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
} else {
throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
}
}

如果注入的属性是on开头的,说明这个配置项是用于注册event的handler,__set方法会调用on函数将handler保存起来,以备后续trigger调用:


public function on($name, $handler, $data = null, $append = true)
{

$this->ensureBehaviors();
if ($append || empty($this->_events[$name])) {
$this->_events[$name][] = [$handler, $data];
} else {
array_unshift($this->_events[$name], [$handler, $data]);
}
}

其中handler必须是callable的,也就是可以作为第一个参数传给call_user_func发起调用,最简单的就是一个全局函数的名字。
接下来,业务代码可以通过显式的调用trigger埋点引入切面,通过配置文件on规则注入的handler就会被自动的调用,AOP就此实现。
下面是Application类的run方法,它多次触发event实现切面编程:

php
public function run()
{
try {

$this->state = self::STATE_BEFORE_REQUEST;
$this->trigger(self::EVENT_BEFORE_REQUEST);

$this->state = self::STATE_HANDLING_REQUEST;
$response = $this->handleRequest($this->getRequest());

$this->state = self::STATE_AFTER_REQUEST;
$this->trigger(self::EVENT_AFTER_REQUEST);

$this->state = self::STATE_SENDING_RESPONSE;
$response->send();

$this->state = self::STATE_END;

return $response->exitStatus;

} catch (ExitException $e) {

$this->end($e->statusCode, isset($response) ? $response : null);
return $e->statusCode;

}
}

behavior行为
在yii2里,behavior提供了2种能力,一个是给现有类注入额外的方法和属性,一个是给现有类注入事件处理函数。
前者其实和 php的trait语法功 能类似,目的是解决PHP单继承的限制,后者提供了另外一种间接注入event handler到component的方式,今天只谈后者。
用户自定义的behavior类要继承自behavior基类,才能拥有上述2个能力:

class Behavior extends Object
{
/**
* @var Component the owner of this behavior
*/
public $owner;


/**
* Declares event handlers for the [[owner]]'s events.
*
* Child classes may override this method to declare what PHP callbacks should
* be attached to the events of the [[owner]] component.
*
* The callbacks will be attached to the [[owner]]'
s events when the behavior is
* attached to the owner; and they will be detached from the events when
* the behavior is detached from the component.
*
* The callbacks can be any of the following:
*
* - method in this behavior: `'handleClick'`, equivalent to `[$this, 'handleClick']`
* - object method: `[$object, 'handleClick']`
* - static method: `['Page', 'handleClick']`
* - anonymous function: `function ($event) { ... }`
*
* The following is an example:
*
* ```php
* [
* Model::EVENT_BEFORE_VALIDATE => 'myBeforeValidate',
* Model::EVENT_AFTER_VALIDATE => 'myAfterValidate',
* ]
*
```
*
* @return array events (array keys) and the corresponding event handler methods (array values).
*/
public function events()
{
return [];
}

/**
* Attaches the behavior object to the component.
* The default implementation will set the [[owner]] property
* and attach event handlers as declared in [[events]].
* Make sure you call the parent implementation if you override this method.
* @param Component $owner the component that this behavior is to be attached to.
*/
public function attach($owner)
{
$this->owner = $owner;
foreach ($this->events() as $event => $handler) {
$owner->on($event, is_string($handler) ? [$this, $handler] : $handler);
}
}

/**
* Detaches the behavior object from the component.
* The default implementation will unset the [[owner]] property
* and detach event handlers declared in [[events]].
* Make sure you call the parent implementation if you override this method.
*/
public function detach()
{
if ($this->owner) {
foreach ($this->events() as $event => $handler) {
$this->owner->off($event, is_string($handler) ? [$this, $handler] : $handler);
}
$this->owner = null;
}
}
}

$owner记录该行为对象注入到了哪个component对象,events函数返回要注入到component的事件处理函数,而attach和detach则用于将behavior对象注入到component中,它同时会将event handler通过component的on方法(此前讲过)注册进去。
回到此前component的__set方法,除了on规则外还有一个判断as规则的分支,该配置可以为component注入一个行为类,component会将该行为类对象通过了attachBehaviors方法保存起来以备后用:


private function attachBehaviorInternal($name, $behavior)
{

if (!($behavior instanceof Behavior)) {
$behavior = Yii::createObject($behavior);
}
if (is_int($name)) {
$behavior->attach($this);
$this->_behaviors[] = $behavior;
} else {
if (isset($this->_behaviors[$name])) {
$this->_behaviors[$name]->detach();
}
$behavior->attach($this);
$this->_behaviors[$name] = $behavior;
}
return $behavior;
}

该方法首先调用behavior对象的attach方法将behavior关联到component,并将其随带的event handler注册到component中。
这样在后续访问component中不存在的属性或者调用不存在的方法时,__get和_call方法就可以到_behaviors数组中遍历每个行为对象,如果这些行为对象中存在该属性或方法,那么就可以保证正常访问。


//__get方法:
public function __get($name)
{

$getter = 'get' . $name;
if (method_exists($this, $getter)) {
// read property, e.g. getName()
return $this->$getter();
} else {
// behavior property
$this->ensureBehaviors();
foreach ($this->_behaviorsas $behavior) {
if ($behavior->canGetProperty($name)) {
return $behavior->$name;
}
}
}
if (method_exists($this, 'set' . $name)) {
throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name);
} else {
throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
}
}

//__call方法:
public function __call($name, $params)
{

$this->ensureBehaviors();
foreach ($this->_behaviorsas $object) {
if ($object->hasMethod($name)) {
return call_user_func_array([$object, $name], $params);
}
}
throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()");
}

Yii2作者设计模式能力很强,我们应加强对该框架的深入学习。
利用event,基于AOP思想,实现了一套切面编程的能力。
利用behavior,实现了类似trait的代码复用能力。


推荐阅读
  • 本项目在Java Maven框架下,利用POI库实现了Excel数据的高效导入与导出功能。通过优化数据处理流程,提升了数据操作的性能和稳定性。项目已发布至GitHub,当前最新版本为0.0.5。该项目不仅适用于小型应用,也可扩展用于大型企业级系统,提供了灵活的数据管理解决方案。GitHub地址:https://github.com/83945105/holygrail,Maven坐标:`com.github.83945105:holygrail:0.0.5`。 ... [详细]
  • 深入解析Gradle中的Project核心组件
    在Gradle构建系统中,`Project` 是一个核心组件,扮演着至关重要的角色。通过使用 `./gradlew projects` 命令,可以清晰地列出当前项目结构中包含的所有子项目,这有助于开发者更好地理解和管理复杂的多模块项目。此外,`Project` 对象还提供了丰富的配置选项和生命周期管理功能,使得构建过程更加灵活高效。 ... [详细]
  • Django框架下的对象关系映射(ORM)详解
    在Django框架中,对象关系映射(ORM)技术是解决面向对象编程与关系型数据库之间不兼容问题的关键工具。通过将数据库表结构映射到Python类,ORM使得开发者能够以面向对象的方式操作数据库,从而简化了数据访问和管理的复杂性。这种技术不仅提高了代码的可读性和可维护性,还增强了应用程序的灵活性和扩展性。 ... [详细]
  • 本文将介绍一种扩展的ASP.NET MVC三层架构框架,并通过使用StructureMap实现依赖注入,以降低代码间的耦合度。该方法不仅能够提高代码的可维护性和可测试性,还能增强系统的灵活性和扩展性。通过具体实践案例,详细阐述了如何在实际开发中有效应用这一技术。 ... [详细]
  • 内网渗透技术详解:PTH、PTT与PTK在域控环境中的应用及猫盘内网穿透配置
    本文深入探讨了内网渗透技术,特别是PTH、PTT与PTK在域控环境中的应用,并详细介绍了猫盘内网穿透的配置方法。通过这些技术,安全研究人员可以更有效地进行内网渗透测试,解决常见的渗透测试难题。此外,文章还提供了实用的配置示例和操作步骤,帮助读者更好地理解和应用这些技术。 ... [详细]
  • 从零起步:使用IntelliJ IDEA搭建Spring Boot应用的详细指南
    从零起步:使用IntelliJ IDEA搭建Spring Boot应用的详细指南 ... [详细]
  • 本题库精选了Java核心知识点的练习题,旨在帮助学习者巩固和检验对Java理论基础的掌握。其中,选择题部分涵盖了访问控制权限等关键概念,例如,Java语言中仅允许子类或同一包内的类访问的访问权限为protected。此外,题库还包括其他重要知识点,如异常处理、多线程、集合框架等,全面覆盖Java编程的核心内容。 ... [详细]
  • 如何利用Git实现高效的多人协作开发(远程仓库应用实例)——Ares Zhao
    Git作为一种分布式版本控制系统,每位开发者都是本地仓库的管理者。然而,为了实现团队间的高效协作,需要将本地的开发成果推送至远程共享仓库,以便其他成员能够同步更新。本文将以GitHub为例,详细介绍如何通过设置和使用远程仓库,实现多人协作开发的最佳实践。 ... [详细]
  • Java 零基础入门:SQL Server 学习笔记(第21篇)
    Java 零基础入门:SQL Server 学习笔记(第21篇) ... [详细]
  • SQLmap自动化注入工具命令详解(第28-29天 实战演练)
    SQL注入工具如SQLMap等在网络安全测试中广泛应用。SQLMap是一款开源的自动化SQL注入工具,支持12种不同的数据库,具体支持的数据库类型可在其插件目录中查看。作为当前最强大的注入工具之一,SQLMap在实际应用中具有极高的效率和准确性。 ... [详细]
  • 本文提供了 RabbitMQ 3.7 的快速上手指南,详细介绍了环境搭建、生产者和消费者的配置与使用。通过官方教程的指引,读者可以轻松完成初步测试和实践,快速掌握 RabbitMQ 的核心功能和基本操作。 ... [详细]
  • 深入解析MyBatis的高级映射技术
    在前一章节中,我们探讨了MyBatis的基本对象映射方法,其中对象属性与数据库表字段之间实现了直接的一对一映射。然而,在实际开发中,这种简单的映射方式往往难以满足复杂业务需求。本文将深入分析MyBatis的高级映射技术,介绍如何通过配置和注解实现更为灵活的对象与数据库表之间的映射关系,包括嵌套结果、联合查询和动态SQL等高级功能,以提高开发效率和代码可维护性。 ... [详细]
  • 本文详细介绍了 Ansible Ad-Hoc 命令的使用方法,基于官方文档进行了中文翻译。Ad-Hoc 命令允许用户通过 `usr/bin/ansible` 快速执行一次性任务,适用于快速部署、配置管理和故障排查等场景。文中通过多个实例演示了 Ad-Hoc 命令的具体应用,帮助读者更好地理解和掌握这一强大工具。 ... [详细]
  • MD5,即消息摘要算法5,是一种广泛应用于确保数据传输完整性的哈希算法。作为计算机领域常用的杂凑算法之一,MD5在多种主流编程语言中均有实现。此外,本文还详细解析了SHA、DES、3DES、AES、RSA和ECC等加密算法的基本原理和应用场景,为读者提供全面的加密技术概览。 ... [详细]
  • 在Linux系统中Nginx环境下SSL证书的安装步骤与WordPress CDN的高级配置指南
    在Linux系统中,Nginx环境下安装SSL证书的具体步骤及WordPress CDN的高级配置指南。首先,安装SSL证书需要准备两个关键配置文件,并建议在操作前备份相关服务器配置文件,以确保数据安全。随后,本文将详细介绍如何在Nginx中正确配置SSL证书,以及如何优化WordPress的CDN设置,提升网站性能和安全性。 ... [详细]
author-avatar
手机用户2502918695
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有