依賴注入可以說是laravel裡非常重要的特性,實踐出來的機制稱之為容器,或是ioc,adonisjs也有實踐這個功能,然而adonisjs實踐的可不只有容器,同時還有自動注入。只是上面兩個功能,在官網上幾乎都是一筆帶過,甚至自動注入官方網站根本就沒有提到,要不是去看4版的文件,不然就是去參考laravel的文件,再不就是去問狗了。今天再就此作一個紀錄。
adonisjs的container被包在Application這個全域變數當中,可以隨時調用
import Application from '@ioc:Adonis/Core/Application'
export default class TestController {
async test({ request }: HttpContextContract) {
Application.container
}
}
官方做法是建議在AppProvider.ts裡,將所有會用到的物件都加以宣告,要使用時就可以直接呼叫相對應的key
// 註冊如下
export default class AppProvider {
constructor(protected app: ApplicationContract) {}
public register() {
this.app.container.bind("test", () => new Object())
}
}
// 使用如下
import Application from '@ioc:Adonis/Core/Application'
export default class TestController {
async test({ request }: HttpContextContract) {
const object = Application.container.make("test")
}
}
這個好處是降低物件之間的依賴,同時減少new複雜物件的重工。這網路都很多教學我就不再贅述,有興趣可以自行去查關鍵字"依賴注入"或"容器"。
然而每個要使用的物件service, repository, tools全部都要註冊這會不會太麻煩了一點。這會導致AppProvider.ts過於龐大,一日小編在網路查找資訊時看到了曙光。
import { inject } from "@adonisjs/fold"
@inject()
export default class TestController {
constructor(private service: TestService) {}
async test({ request }: HttpContextContract) {
return this.service.get({})
}
}
adonisjs使用了ts的decorator,撰寫了inject方法,使用方式很簡單,源碼也不長,可以使用在class或method上,參數可打給不打(小編用到目前還沒給過),使用在class上就會把建構子上宣告的變數,依照類別自動注入,非常快速及方便。唯一要注意的是,被注入的物件上層,建議也使用inject decorator,不然宣告起來會很費事,範利如下
import { inject } from "@adonisjs/fold"
@inject()
export default class TestService {
constructor(private repo: TestRepository) {}
get({ }) {
return this.repo.get()
}
}
// 如果不使用inject情境
export default class TestController {
constructor(private service: TestService) {
this.service = new TestService(new TestRepository())
}
async test({ request }: HttpContextContract) {
return this.service.get({})
}
}
這還是只有兩層的情況下,如果TestRepository的建構子也有參數,那你還是乖乖使用inject完事吧。
只要container跟inject,基本上可以說是想用什麼就自動注入什麼,相依性可以說大大降低。可以說在專案上幾乎沒有container出馬的份,inject就可以完成所有工作。但container當然還是有他的市場,adonisjs改寫過轉譯器,我們常常在套件中看到@ios:開頭的套件,例如
import Application from '@ioc:Adonis/Core/Application'
這當然不是這個套件真實的名稱跟路徑,是因為轉譯器的效果。只要發現該抬頭的字串,轉譯器就會自動去尋找container所註冊過的同名物件,我們舉昨天class-validator為例子
// 實際註冊名稱如下
export default class ClassValidatorProvider {
constructor(protected app: ApplicationContract) {}
public async boot() {
this.bindClassValidator();
}
private bindClassValidator() {
const adonisValidator = this.app.container.use("Adonis/Core/Validator");
// 註冊名稱為 Adonis/ClassValidator
this.app.container.singleton("Adonis/ClassValidator", () => {
return {
validate: require("../src").validate,
...adonisValidator,
};
});
}
}
// 專案經由npm install後並掛載provider,呼叫方式如下
import { validate } from "@ioc:Adonis/ClassValidator"
轉譯器讓import也可以直接把container所註冊的所有物件,不用透過專案內的container也可以呼叫使用,這實在是非常聰明且方便的做法,也有降低耦合的效果。不過這缺點就是必須是npm套件,所以專案內還是用inject就好吧
3/25更新
我在某個教學網站上看到,其實可以不用上到npm,直接本地provider bind完就可以在專案中直接使用,至於有沒有成功那就留給大家有空去驗證了
參考網站:https://adocasts.com/lessons/service-providers-and-the-ioc-container
4/28更新
更多使用情境可以參考
https://dev.to/serjoagronovdev/how-to-inject-services-in-adonisjs-v5-constructor-method-2gge
沒有留言:
張貼留言