摘要:由于項目緊,人手不足,必須以最小的代價解決當前問題。引入作為一門好的語言,是不自帶的。通過斷點進入,發現會對的文件進行語法解析,并根據注冊的對相關的類生成類。
項目背景
目前開發的WEB軟件里有這一個功能,PHP訪問API操作數據倉庫,剛開始數據倉庫小,沒發現問題,隨著數據越來越多,調用API時常超時(60s)。于是決定采用異步請求,改為60s能返回數據則返回,不能則返回一個異步ID,然后輪詢是否完成統計任務。由于項目緊,人手不足,必須以最小的代價解決當前問題。
方案選擇從新分析需求,并改進代碼
采用AOP方式改動程序
從新做需求分析,以及詳細設計,并改動代碼,需要產品,架構,前端,后端的支持。會驚動的人過多,在資源緊張的情況下是不推薦的。
采用AOP方式,不改動原有代碼邏輯,只需要后端就能完成大部分任務了。后端用AOP切入請求API的方法,通過監聽API返回的結果來控制是否讓其繼續運行原有的邏輯(API在60s返回了數據),或者是進入離線任務功能(API報告統計任務不能在60s內完成)。
Debian,php-fpm-7.0,ThinkPHP-5.10。
引入AOP作為一門zui好的語言,PHP是不自帶AOP的。那就得安裝AOP-PHP拓展,當我打開pecl要下載時,傻眼了,全是bate版,沒有顯示說明支持php7。但我還是抱著僥幸心理,找到了git,發現4-5年沒更新了,要不要等一波更新,哦,作者在issue里說了有時間就開始兼容php7。
好吧,狠話不多說,下一個方案:Go!AOP.看了下git,作者是個穿白體恤,喜歡山峰的大帥哥,基本每個issue都會很熱心回復。
composer require goaop/framework
ThinkPHP5 對composer兼容挺不錯的哦,(到后面,我真想揍ThinkPHP5作者)這就裝好了,怎么用啊,git上的提示了簡單用法。我也就照著寫了個去切入controller。
aspectAction(); } public function aspectAction() { return "hello"; } }
定義aspect
aspectAction(*))") */ 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()), "
"; } }
啟用aspect
registerAspect(new MonitorAspect()); } }
go-aop 核心服務配置
init([ "debug" => true, "appDir" => __DIR__ . "./../../../", "cacheDir" => __DIR__ . "./../../../runtime/aop_cache", "includePaths" => [ __DIR__ . "./../../tests/controller", __DIR__ . "./../../../thinkphp/library/think/model" ], "excludePaths" => [ __DIR__ . "./../../aspect", ] ]); return $params; } }
配置模塊init鉤子,讓其啟動 go-aop
[], // 應用開始 "app_begin" => [], // 模塊初始化 "module_init" => [ "app estsehaviorBootstrap" ], // 操作開始執行 "action_begin" => [], // 視圖內容過濾 "view_filter" => [], // 日志寫入 "log_write" => [], // 應用結束 "app_end" => [], ];兼容測試
好了,訪問 http://127.0.0.1/tests/test1/... 顯示:
hello
這不是預期的效果,在aspect定義了,訪問該方法前,會輸出方法的更多信息信息。
像如下內容才是預期
Calling Before Interceptor for method: app estscontrollerTest1->aspectAction() with arguments: []
上他官方Doc看看,是一些更高級的用法。沒有講go-aop的運行機制。
上git上也沒看到類似issue,額,發現作者經常在issue里回復:試一試demo。也許我該試試demo。
我采用的是LNMP技術棧。
假設這里有臺Ubuntu你已經配置好了LNMP環境
下載代碼
配置nginx
# file: /usr/share/etc/nginx/conf.d/go-aop-test.conf server { listen 8008; # listen 443 ssl; server_name 0.0.0.0; root "/usr/share/nginx/html/app/vendor/lisachenko/go-aop-php/demos"; index index.html index.htm index.php; charset utf-8; access_log /var/log/nginx/go-aop-access.log; error_log /var/log/nginx/go-aop-error.log notice; sendfile off; client_max_body_size 100m; location ~ .php(.*)$ { include fastcgi_params; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param PATH_INFO $fastcgi_path_info; # fastcgi_param SCRIPT_FILENAME /var/www/html/app/vendor/lisachenko/go-aop-php/demos$fastcgi_script_name; #docker的配置 fastcgi_param SCRIPT_FILENAME /usr/share/nginx/html/api/vendor/lisachenko/go-aop-php/demos$fastcgi_script_name; fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info; fastcgi_split_path_info ((?U).+.php)(/?.+)$; } }
接下來要調整下代碼
訪問 http://127.0.0.1:8008 試試,(估計大家都遇到了這個)
這個報錯信息提示找不到這個類。來到報錯的文件里。這文件使用了use找不到類,就是autoload出問題了,看到 vendor/lisachenko/go-aop-php/demos/autoload.php 這個文件。
add("Demo", __DIR__); }
可以看到這個代碼第一行沒找到vendor下的autoload。我們做如下調整
add("Demo", __DIR__); }
再試試,demo運行起來了。
嘗試了下,運行成功
通過以上的輸出,可以得出demo里是對方法運行前成功捕獲。為什么在thinkphp的controller里運行就不成功呢。我決定采用斷點進行調試。
通過斷點我發現了這個文件
original->findFile($class)) { $isInternal = false; foreach ($this->internalNamespaces as $ns) { if (strpos($class, $ns) === 0) { $isInternal = true; break; } } include ($isInternal ? $file : FilterInjectorTransformer::rewrite($file)); } }
這是一個autoload,每個類的載入都會經過它,并且會對其判斷是否為內部類,不是的都會進入后續的操作。通過斷點進入 FilterInjectorTransformer,發現會對load的文件進行語法解析,并根據注冊的annotation對相關的類生成proxy類。說道這,大家就明白了go-aop是如何做到切入你的程序了吧,生成的proxy類,可以在你配置的cache-dir(我配置的是./runtime/aop_cache/)里看到。
同時./runtime/aop_cache/ 文件夾下也生成了很多東西,通過查看aop_cache文件內產生了與Test1文件名相同的文件,打開文件,發現它代理了原有的Test1控制器。這一系列信息,可以得出,Go!AOP 通過"劫持" composer autoload 讓每個類都進過它,根據aspect的定義來決定是否為其創建一個代理類,并植入advice。
額,ThinkPHP5是把composer autoload里的東西copy出來,放到自己autoload里,然后就沒composer啥事了。然后go-aop一直等不到composer autoload下發的命令,自然就不能起作用了,so,下一步
在ThinkPHP5里,默認有且只會注冊一個TP5內部的 Loader,并不會把include請求下發給composer的autoload。所以,為其讓go-aop起作用,那么必須讓讓include class的請求經過 AopComposerLoad.
我們看看這個文件
loadClass($class); return class_exists($class, false); }); $loader[0] = new AopComposerLoader($loader[0]); } spl_autoload_unregister($loaderToUnregister); } unset($loader); foreach ($loaders as $loader) { spl_autoload_register($loader); } }
這個文件里有個類型檢測,檢測autoload callback是否為Classloader類型,然而ThinkPHP5不是,通過斷點你會發現ThinkPHP5是一個字符串數組,so,這里也就無法把go-aop注冊到class loader的callback當中了。
這里就要提一下PHP autoload機制了,這是現代PHP非常重要的一個功能,它讓我們在用到一個類時,通過名字能自動加載文件。我們通過定義一定的類名規則與文件結構目錄,再加上能實現以上規則的函數就能實現自動加載了。在通過 spl_autoload_register 函數的第三個參數 prepend 設置為true,就能讓其排在在TP5的loader前面,先一步被調用。
依照如上原理,就可以做如下改進
這個是為go-aop包裝的新autoload,本質上是在原來的ThinkPHP5的loader上加了一個殼而已。
getMethod("findFile"); $method->setAccessible(true); $file = $method->invoke($obj, $class); return $file; } }
loadClass($class); return class_exists($class, false); }); $aopLoader = new AopComposerLoader($thinkLoader); spl_autoload_register([$aopLoader, "loadClass"], false, true); return $params;
在這里我們做了一個autload 并直接把它插入到了最前面(如果項目內還有其他autloader,請注意他們的先后順序)。
最后現在我們再訪問一下 http://127.0.0.1/tests/test1/... 你就能看到來自 aspect 輸出的信息了。
最后我們做個總結:
PHP7 目前沒有拓展實現的 AOP。
ThinkPHP5 有著自己的 Autoloader。
Go!AOP 的AOP實現依賴Class Autoload的 callback,通過替換原文件指向Proxy類實現。
ThinkPHP5 整合 Go!AOP 需要調整 autoload。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/26291.html
摘要:環境集成環境文件目錄目錄入口文件第一步修改文件,將配置文件包含進來點擊,用記事本打開,定位到此處。在第一個例子中只改動了和,這個配置保證了我們在開啟后仍然可以訪問目錄。 環境windows7 , wampserver集成環境 , thinkphp5.0文件wamp目錄:D:/wamp/wwwthinkphp目錄:F:/tp5thinkphp入口文件:F:/tp5/public/inde...
摘要:如上為三個模塊行為行為是在預先定義好的一個應用位置執行的一些操作。發生作用的位置稱之為鉤子,當應用程序運行到這個鉤子的時候,就會被攔截下來,統一執行相關的行為。給某一個鉤子綁定相關行為就成了一種類編程的思想。 ThinkPhp5.0 Composer: php世界里的包管理器 目錄結構 project 應用部署目錄 ├─application 應用目錄(可設置) ...
摘要:總結動態代理的相關原理已經講解完畢,接下來讓我們回答以下幾個思考題。 【干貨點】 此處是【好好面試】系列文的第12篇文章。文章目標主要是通過原理剖析的方式解答Aop動態代理的面試熱點問題,通過一步步提出問題和了解原理的方式,我們可以記得更深更牢,進而解決被面試官卡住喉嚨的情況。問題如下 SpringBoot默認代理類型是什么 為什么不用靜態代理 JDK動態代理原理 CGLIB動態代理...
摘要:索性讀一下它的源碼。行載入類載入類,這個類比較重要,實現了自動加載。注冊錯誤和異常處理機制加載慣例配置文件接下來我們看一下自動加載的實現方法。所以借助此函數可以達到自動加載。博客鏈接解讀源碼一自動加載 聽說 TP5 已經 RC4 了,曾經在 RC3 的時候用它寫過一個小東西。官方說從 RC4 以后改動不是太大。索性讀一下它的源碼。然后順便記錄一下,如有錯漏,請路過大神多多指正! 入口 ...
摘要:最近做項目,做分頁的功能,使用給后臺發送數據,使用接口還是工具都可獲取數據,唯獨獲取不到經過排除,發現這與的傳參格式有關系在使用時,要注意到配置選項中包含和兩者,以為他們是相同的,實則不然。因為是添加到的請求字符串中的,用于請求。 最近做vue項目,做分頁的功能,使用post給后臺發送數據,使用接口還是工具(postman)都可獲取數據,唯獨axios獲取不到;經過排除,發現這與axi...
閱讀 2987·2021-11-23 09:51
閱讀 3005·2021-11-02 14:46
閱讀 869·2021-11-02 14:45
閱讀 2746·2021-09-23 11:57
閱讀 2498·2021-09-23 11:22
閱讀 1927·2019-08-29 16:29
閱讀 746·2019-08-29 16:16
閱讀 944·2019-08-26 13:44