YII2集成GOAOP,實現面向方面編程!


引言:
  軟件開發的目標是要對世界的部分元素或者信息流建立模型,實現軟件系統的工程需要將系統分解成可以創建和管理的模塊。於是出現了以系統模塊化特性的面向對象程序設計技術。模塊化的面向對象編程極度地提高了軟件系統的可讀性、復用性和可擴展性。向對象方法的焦點在於選擇對象作為模塊的主要單元,並將對象與系統的所有行為聯系起來。對象成為問題領域和計算過程的主要元素。但面向對象技術並沒有從本質上解決軟件系統的可復用性。創建軟件系統時,現實問題中存在着許多橫切關注點,比如安全性檢查、日志記錄、性能監控,異常處理等,它們的實現代碼和其他業務邏輯代碼混雜在一起,並散落在軟件不同地方(直接把處理這些操作的代碼加入到每個模塊中),這無疑破壞了OOP的"單一職責"原則,模塊的可重用性會大大降低,這使得軟件系統的可維護性和復用性受到極大限制。這時候傳統的OOP設計往往采取的策略是加入相應的代理(Proxy)層來完成系統的功能要求,但這樣的處理明顯使系統整體增加了一個層次的划分,復雜性也隨之增加,從而給人過於厚重的感覺。由此產生了面向方面編程(AOP)技術。這種編程模式抽取出散落在軟件系統各處的橫切關注點代碼,並模塊化,歸整到一起,這樣進一步提高軟件的可維護性、復用性和可擴展性。

AOP簡介:
AOP: Aspect Oriented Programming 面向切面編程。
  面向切面編程(也叫面向方面):Aspect Oriented Programming(AOP),是目前軟件開發中的一個熱點。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
  AOP是OOP的延續,是(Aspect Oriented Programming)的縮寫,意思是面向切面(方面)編程。
  主要的功能是:日志記錄,性能統計,安全控制,事務處理,異常處理等等。
  主要的意圖是:將日志記錄,性能統計,安全控制,事務處理,異常處理等代碼從業務邏輯代碼中划分出來,通過對這些行為的分離,我們希望可以將它們獨立到非指導業務邏輯的方法中,進而改變這些行為的時候不影響業務邏輯的代碼。
  可以通過預編譯方式和運行期動態代理實現在不修改源代碼的情況下給程序動態統一添加功能的一種技術。AOP實際是GoF設計模式的延續,設計模式孜孜不倦追求的是調用者和被調用者之間的解耦,AOP可以說也是這種目標的一種實現。
假設把應用程序想成一個立體結構的話,OOP的利刃是縱向切入系統,把系統划分為很多個模塊(如:用戶模塊,文章模塊等等),而AOP的利刃是橫向切入系統,提取各個模塊可能都要重復操作的部分(如:權限檢查,日志記錄等等)。由此可見,AOP是OOP的一個有效補充。
注意:AOP不是一種技術,實際上是編程思想。凡是符合AOP思想的技術,都可以看成是AOP的實現。

AOP 的基本概念:
在面向對象編程中,類,對象,封裝,繼承,多態等概念是描述面向對象思想主要術語。與此類似,在面向方面編程中,同樣存在着一些基本概念:
       聯結點(JointPoint) :一個聯結程序執行過程中的一個特定點。典型的聯結點有:調用一個方法;方法執行這個過程本身;類初始化;對象初始化等。聯結點是 AOP 的核心概念之一,它用來定義在程序的哪里通過 AOP 加入新的邏輯。
        切入點(Pointcut) :一個切入點是用來定義某一個通知該何時執行的一組聯結點。通過定義切入點,我們可以精確地控制程序中什么組件接到什么通知。上面我們提到,一個典型的聯結點是方法調用,而一個典型的切入點就是對某一個類的所在方法調用的集合。通常我們會通過組建復雜的切入點來控制通知什么時候被執行。
        通知(Advice) :在某一個特定的聯結點處運行的代碼稱為“通知”。通知有很多種,比如
在聯結點之前執行的前置通知(before advice)和在聯結點之后執行的后置通知(after advice) 。
       方面(Aspect) :通知和切入點的組合叫做方面,所以,方面定義了一段程序中應該包括的邏輯,以及何時應該執行該邏輯。
       織入(Weaving) :織入是將方面真正加入程序代碼的過程。對於靜態 AOP 方案而言,織入是在編譯時完成的,通常是在編譯過程中增加一個步驟。類似的,動態 AOP 方案則是在程序運行是動態織入的。
       目標(Target) :如果一個對象的執行過程受到某一個 AOP 的修改,那么它就叫一個目標對象。目標對象通常也稱為被通知對象。
       引入(Introduction) :   通過引入,可以在一個對象中加入新的方法或屬性,以改變它的結構,這樣即使該對象的類沒有實現某一個接口,也可以修改它,使之成為該接口的一個實現。   

       靜態和動態:靜態 AOP 和動態 AOP 兩者之間的區別主要在於什么時間織入,以及如何織入。最早的 AOP 實現大多都是靜態的。在靜態 AOP 中,織入是編譯過程的一個步驟。用Java 的術語說,靜態 AOP 通過直接對字節碼進行操作,包括修改代碼和擴展類,來完成織入過程。顯然,這種辦法生成的程序性能很好,因為最后的結果就是普通的 Java 字節碼,在運行時不再需要特別的技巧來確定什么時候應該執行通知。這種方法的缺點是,如果想對方面做什么修改,即使只是加入一個新的聯結點,都必須重新編譯整個程序。AspectJ 是靜態 AOP 的一個典型例子。與靜態 AOP 不同,動態 AOP 中織入是在運行時動態完成的。織入具體是如何完成的,各個實現有所不同。Spring AOP 采取的方法是建立代理,然后代理在適當的時候執行通知。動態 AOP 的一個弱點就在於,其性能一般不如靜態 AOP。而動態AOP 的主要優點在於可以隨時修改程序的所有方面,而不需重新編譯目標。

AOP實踐:
YII2框架本身擁有一個功能,叫做行為.它可以動態的為當前的類附加額外的功能,但這種功能在代碼層級結構是靜態的,有侵入性的。

下面以YII2框架集成go!aop庫為例,介紹在YII2中如何實現AOP編程.(go!aop簡介,可以參考go!aop的官網.)

由於YII框架擁有自己的類加載器,所在集成go!aop的時候,不能正常的工作,所以要將其禁用掉,使用composer提供的類加載器。
如下代碼所示(這里使用YII2高級應用模板):

1、找到  spl_autoload_register(['Yii', 'autoload'], true, true);  (PROJECT_PATH/vendor/yiisoft/yii2/Yii.php) 將其禁用掉.


2、執行  composer require goaop/framework


3、修改composer.json文件,加入如下代碼段:

 "autoload": {
        "psr-4": {
          "backend\\": "backend//",
          "frontend\\": "frontend//",
          "common\\": "common//"
        }
  }


4、 在frontend 目錄下創建一個components是目錄,並新建一個類AopAspectKernel,例如:

namespace frontend\components;
use frontend\aspects\MonitorAspect;
use Go\Core\AspectContainer;
use Go\Core\AspectKernel;
class AopAspectKernel extends AspectKernel
{
        protected function configureAop(AspectContainer $container)
        {
            $container->registerAspect(new MonitorAspect());
        }
}

 
5、在forntend目錄下在新建一個類InitAopComponent,並使其實現BootstrapInterface,使其可以在YII2框架引導時被自動引導

namespace frontend\components;
use yii\base\BootstrapInterface;
class InitAopComponent implements BootstrapInterface
{
        public function bootstrap($app)
        {
            print_r(\Yii::$app->params['aop']);
            $applicationAspectKernel = AopAspectKernel::getInstance();
            $applicationAspectKernel->init(\Yii::$app->params['aop']);
        }
}

 
6、在frontend/config/params.php中新增如下代碼:

 'aop' => [
        'debug' => true,
        'appDir' => dirname(__DIR__),
        'cacheDir' => dirname(__DIR__) . '/runtime/aop',
        'includePaths' => [
            dirname(__DIR__)
        ]
    ]

 
7、在frontend下面新建aspects目錄,並新建類MonitorAspect,代碼如下:

namespace frontend\aspects;

use Go\Aop\Aspect;
use Go\Aop\Intercept\MethodInvocation;
use Go\Lang\Annotation\Before;
class MonitorAspect implements Aspect
{
        /**
         * Method that will be called before real method
         *
         * @param MethodInvocation $invocation Invocation
         * @Before("execution(public frontend\components\AopTestComponent->*(*))")
         */
        public function beforeMethodExecution(MethodInvocation $invocation)
        {
            $obj = $invocation->getThis();
            echo 'Calling Before Interceptor for method: ',
            is_object($obj) ? get_class($obj) : $obj,
            $invocation->getMethod()->isStatic() ? '::' : '->',
            $invocation->getMethod()->getName(),
            '()',
            ' with arguments: ',
            json_encode($invocation->getArguments()),
            "<br>\n";
        }
}

 
9、修改frontend/config/main.php文件,並在components數組下新增一個key,代碼如下:

 'components'=>[
        'aop' => [
            'class' => 'frontend\components\InitAopComponent'
        ]
    ]

 
10、修改frontend/config/main.php文件,並在bootstrap數組下新增aop值,代碼如下:

'bootstrap'=>['log','aop']


至此,YII2整合go!aop完成...

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM