# AOP 面向切面編程

## 概念

AOP 為 `Aspect Oriented Programming` 的縮寫，意為：`面向切面編程`，通過動態代理等技術實現程序功能的統一維護的一種技術。AOP 是 OOP 的延續，也是 Hyperf 中的一個重要內容，是函數式編程的一種衍生範型。利用 AOP 可以對業務邏輯的各個部分進行隔離，從而使得業務邏輯各部分之間的耦合度降低，提高程序的可重用性，同時提高了開發的效率。   

用通俗的話來講，就是在 Hyperf 裏可以通過 `切面(Aspect)` 介入到任意類的任意方法的執行流程中去，從而改變或加強原方法的功能，這就是 AOP。

> 注意這裏所指的任意類並不是完全意義上的所有類，在 Hyperf 啟動初期用於實現 AOP 功能的類自身不能被切入。

## 介紹

相對於其它框架實現的 AOP 功能的使用方式，我們進一步簡化了該功能的使用不做過細的劃分，僅存在 `環繞(Around)` 一種通用的形式：

- `切面(Aspect)` 為對流程織入的定義類，包括要介入的目標，以及實現對原方法的修改加強處理
- `代理類(ProxyClass)` ，每個被介入的目標類最終都會生成一個代理類，來達到執行 `切面(Aspect)` 方法的目的

## 定義切面(Aspect)

每個 `切面(Aspect)` 必須實現 `Hyperf\Di\Aop\AroundInterface` 接口，並提供 `public` 的 `$classes` 和 `$annotations` 屬性，為了方便使用，我們可以通過繼承 `Hyperf\Di\Aop\AbstractAspect` 來簡化定義過程，我們通過代碼來描述一下。

```php
<?php
namespace App\Aspect;

use App\Service\SomeClass;
use App\Annotation\SomeAnnotation;
use Hyperf\Di\Annotation\Aspect;
use Hyperf\Di\Aop\AbstractAspect;
use Hyperf\Di\Aop\ProceedingJoinPoint;

/**
 * @Aspect
 */
class FooAspect extends AbstractAspect
{
    // 要切入的類，可以多個，亦可通過 :: 標識到具體的某個方法，通過 * 可以模糊匹配
    public $classes = [
        SomeClass::class,
        'App\Service\SomeClass::someMethod',
        'App\Service\SomeClass::*Method',
    ];
    
    // 要切入的註解，具體切入的還是使用了這些註解的類，僅可切入類註解和類方法註解
    public $annotations = [
        SomeAnnotation::class,
    ];

    public function process(ProceedingJoinPoint $proceedingJoinPoint)
    {
        // 切面切入後，執行對應的方法會由此來負責
        // $proceedingJoinPoint 為連接點，通過該類的 process() 方法調用原方法並獲得結果
        // 在調用前進行某些處理
        $result = $proceedingJoinPoint->process();
        // 在調用後進行某些處理
        return $result;
    }
}
```

每個 `切面(Aspect)` 必須定義 `@Aspect` 註解或在 `config/autoload/aspects.php` 內配置均可發揮作用。

> 使用 `@Aspect` 註解時需 `use Hyperf\Di\Annotation\Aspect;` 命名空間；  

您也可以通過 `@Aspect` 註解本身的屬性來完成切入目標的配置，通過下面註解的形式可以達到與上面的示例一樣的目的：

```php
<?php
namespace App\Aspect;

use App\Service\SomeClass;
use App\Annotation\SomeAnnotation;
use Hyperf\Di\Annotation\Aspect;
use Hyperf\Di\Aop\AbstractAspect;
use Hyperf\Di\Aop\ProceedingJoinPoint;

/**
 * @Aspect(
 *   classes={
 *      SomeClass::class,
 *      "App\Service\SomeClass::someMethod",
 *      "App\Service\SomeClass::*Method"
 *   },
 *   annotations={
 *      SomeAnnotation::class
 *   }
 * )
 */
class FooAspect extends AbstractAspect
{
    public function process(ProceedingJoinPoint $proceedingJoinPoint)
    {
        // 切面切入後，執行對應的方法會由此來負責
        // $proceedingJoinPoint 為連接點，通過該類的 process() 方法調用原方法並獲得結果
        // 在調用前進行某些處理
        $result = $proceedingJoinPoint->process();
        // 在調用後進行某些處理
        return $result;
    }
}
```

> 2.0.3 及以上版本已經可以支持切入 Trait，但要求 PHP 版本 >= 7.3。

## 代理類緩存

所有被 AOP 影響的類，都會在 `./runtime/container/proxy/` 文件夾內生成對應的 `代理類緩存`，是否在啟動時自動生成取決於 `config/config.php` 配置文件中 `scan_cacheable` 配置項的值，默認值為 `false`，如果該配置項為 `true` 則 Hyperf 不會掃描和生成代理類緩存，而是直接以現有的緩存文件作為最終的代理類。如果該配置項為 `false`，則 Hyperf 會在每次啟動應用時掃描註解掃描域並自動的生成對應的代理類緩存，當代碼發生變化時，代理類緩存也會自動的重新生成。

通常在開發環境下，該值為 `false`，這樣更便於開發調試，而在部署生產環境時，我們可能會希望 Hyperf 提前將所有代理類提前生成，而不是使用時動態的生成，可以通過 `php bin/hyperf.php` 命令來生成所有代理類，然後再通過環境變量 `SCAN_CACHEABLE` 為 `true` 修改該配置項的值，以達到啟動時間更短、應用內存佔用更低的目的。   

基於以上，如果您使用 Docker 或 Kubernetes 等虛擬化技術來部署您的應用的話，您可以在鏡像構建階段就生成對應的代理類緩存並寫入到鏡像中去，在運行鏡像實例時，可大大減少啟動時間和應用內存。
