php ioc容器,PHP 在Swoole中使用双IoC容器实现无污染的依赖注入
容器(container)技術(shù)(可以理解為全局的工廠方法), 已經(jīng)是現(xiàn)代項(xiàng)目的標(biāo)配. 基于容器, 可以進(jìn)一步實(shí)現(xiàn)控制反轉(zhuǎn), 依賴注入. Laravel 的巨大成功就是構(gòu)建在它非常強(qiáng)大的IoC容器 illuminate/container 基礎(chǔ)上的. 而 PSR-11 定義了標(biāo)準(zhǔn)的 container , 讓更多的 PHP 項(xiàng)目依賴容器實(shí)現(xiàn)依賴解耦, 面向接口編程.
另一方面, PHP 天生一個(gè)進(jìn)程響應(yīng)一次請求的模型, 已經(jīng)不能完全適應(yīng)開發(fā)的需要. 于是 Swoole, reactPHP, roadrunner 也越來越流行. 它們共同的特點(diǎn)是一個(gè) php worker 進(jìn)程在生命周期內(nèi)要響應(yīng)多個(gè)請求, 甚至同一時(shí)間同時(shí)運(yùn)行多個(gè)請求 (協(xié)程).
在這些引擎上使用傳統(tǒng)只考慮單請求的容器技術(shù), 就容易發(fā)生單例相互污染, 內(nèi)存泄露等問題 (姑且稱之為”IoC容器的請求隔離問題” ). 于是出現(xiàn)了各種策略以解決之.
多輪對話機(jī)器人框架 CommuneChatbot 使用 swoole 做通信引擎, 同時(shí)非常廣泛地使用了容器和依賴注入. 在本項(xiàng)目中使用了 “雙容器策略” 來解決 “請求隔離問題” .
所謂”雙容器策略”, 總結(jié)如下:
同時(shí)運(yùn)行 “進(jìn)程級容器” 與 “請求級容器”
“進(jìn)程級容器” :
傳統(tǒng)的IoC 容器, 例如 Illuminate/container
“請求級容器” :
所有工廠方法注冊到容器的靜態(tài)屬性上
在 worker 進(jìn)程初始化階段 注冊服務(wù)
每個(gè)請求到來后, 實(shí)例化一個(gè)請求容器.
請求中生成的單例, 掛載到容器的動(dòng)態(tài)屬性上.
持有”進(jìn)程級容器”, 當(dāng)綁定不存在時(shí), 到”進(jìn)程級容器” 上查找之.
請求結(jié)束時(shí)進(jìn)行必要清理, 防止內(nèi)存泄露
解決方案的代碼在 https://github.com/thirdgerb/container 創(chuàng)建了一個(gè) composer 包 commune/container
容器的”請求隔離”問題
關(guān)于容器, 控制反轉(zhuǎn)與依賴注入
為防止部分讀者不了這些概念, 簡單說明一下.
所謂容器, 相當(dāng)于一個(gè)全局的工廠. 可以在這里 “注冊” 各種服務(wù)的工廠方法, 再使用容器統(tǒng)一地獲取. 例如
1 $container = newContainer();2
3 //綁定一個(gè)單例
4 $container->singleton(5
//綁定對象的ID, 通常是 interface, 以實(shí)現(xiàn)面向接口編程.
6
UserInterface::class,
7
//生成實(shí)例的工廠方法.
8
function() {9
return new class implementsUserInterface{};10 }11 );12
13 //從容器中獲取實(shí)例
14 $user = $container->get(UserInterfacle::class);15
16 $user instanceof UserInterface; //true
當(dāng)一個(gè)類的實(shí)例在容器中生成, 或者一個(gè)方法被容器調(diào)用時(shí), 就可以方便地實(shí)現(xiàn)依賴注入.
簡單來說, 容器通過反射機(jī)制可獲取目標(biāo)方法的依賴 ( laravel 用反射來獲取 typehint 類型約束, 而 Swoft項(xiàng)目似乎與spring 相似, 是從注釋上獲取的).
然后容器查找是否已注冊了 依賴 (dependency) 的實(shí)現(xiàn) (resolver), 如果已注冊, 就從容器中生成該依賴, 再注入給目標(biāo)方法.
具有依賴注入能力的容器, 我們稱之為 IoC (控制反轉(zhuǎn)) 容器. 關(guān)于IoC 容器的好處不是本文重點(diǎn), 先跳過去了.
IoC 容器的請求隔離問題
容器最典型的應(yīng)用場景之一, 就是持有單例. 但在 swoole 等引擎上, 一個(gè) worker 進(jìn)程要響應(yīng)多個(gè)請求, 單例的數(shù)據(jù)就容易相互污染.
例如我們把 session 的數(shù)據(jù)放在 一個(gè) SessionInterface 中, 每個(gè)邏輯調(diào)用時(shí)都用容器來取:
$sessionInstance = container()->make(SessionInterface::class);
由于單例在容器內(nèi)只生成一次, 那第二次請求時(shí), 容器會(huì)給出第一次請求的session單例, 從而邏輯就亂套了.
所以容器要運(yùn)行在 swoole 等引擎上, 必須做到請求與請求相隔離.
常見的解決策略
由于 Laravel 等使用了IoC 容器的項(xiàng)目能帶來極好的工程體驗(yàn), 而Swoole 能帶來極大的性能提升, 于是有許多試圖結(jié)合兩者的項(xiàng)目, 都面臨了 “請求隔離問題”.
我個(gè)人看到過的解決策略有以下三種, 都能一定程度解決問題, 但也有美中不足之處.
克隆策略:
方案: 每次請求, 克隆一個(gè)新的 container
問題:
要遞歸地 clone 屬性, 才能避免淺拷貝導(dǎo)致的污染
無法區(qū)分進(jìn)程共享的單例, 和請求隔離的單例.
清洗策略:
方案: 每次請求結(jié)束時(shí), 主動(dòng)清洗掉已注冊的單例
問題:
定義類時(shí)就要考慮清洗邏輯, 可能要實(shí)現(xiàn)interface, 耦合較重
swoole 發(fā)展到協(xié)程后, 同時(shí)可能相應(yīng)多個(gè)請求, 清晰策略失效了.
重新注冊:
方案: 每個(gè)請求到來時(shí), 實(shí)例化一個(gè)新容器, 重新注冊所有服務(wù)
問題:
注冊服務(wù)其實(shí)開銷很大, 尤其是需要大量讀文件的初始化(比如翻譯組件)
無法區(qū)分進(jìn)程共享的單例, 和請求隔離的單例.
利用不了 swoole 的優(yōu)勢, 比起多進(jìn)程模型只少了 composer autoloader 的加載.
CommuneChatbot 遇到的請求隔離問題
總結(jié)
以上是生活随笔為你收集整理的php ioc容器,PHP 在Swoole中使用双IoC容器实现无污染的依赖注入的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: oracle11g备份出错,Oracle
- 下一篇: SSH框架(Struts+Spring+