使用 Jerseys HK2 進行基本依賴注入

澤西島(2)使用 HK2 作為其依賴注入(DI)系統。我們可以使用其他注入系統,但其基礎設施使用 HK2 構建,並允許我們在我們的應用程式中使用它。

使用 Jersey 設定簡單依賴注入只需幾行程式碼。比方說,我們有一個服務,我們想要注入我們的資源。

public class GreetingService {
    public String getGreeting(String name) {
        return "Hello " + name + "!";
    }
}

我們希望將此服務注入 Jersey 資源

@Path("greeting")
public class GreetingResource {

    @Inject
    public GreetingService greetingService;

    @GET
    public String get(@QueryParam("name") String name) {
        return this.greetingService.getGreeting(name);
    }
}

為了使注入工作,我們所需要的只是一個簡單的配置

@ApplicationPath("/api")
public class AppConfig extends ResourceConfig {
    public AppConfig() {
        register(GreetingResource.class);
        register(new AbstractBinder() {
            @Override
            protected void configure() {
                bindAsContract(GreetingService.class);
            }
        });
    }
}

在這裡我們說我們想要將 GreetingService 繫結到注入系統,並將它作為可注入的相同類通告。最後一個陳述意味著我們只能將它作為 GreetingService 注入,並且(可能很明顯)不能被任何其他類注入。正如你稍後將看到的,可以更改此設定。

而已。這就是你所需要的。如果你不熟悉此 ResourceConfig 配置(可能你使用的是 web.xml),請參閱 SO Docs 上的在 Jersey 中配置 JAX-RS 主題。

注意: 上面的注入是欄位注入,其中服務被注入資源的欄位。另一種型別的注入是建構函式注入,其中服務被注入到建構函式中

private final GreetingService greetingService;

@Inject   
public GreetingResource(GreetingService greetingService) {
     this.greetingService = greetingService;
}

這可能是首選的方式,而不是現場注入,因為它使資源更容易進行單元測試。建構函式注入不需要任何不同的配置。

好了,現在我們可以說,GreetingService 不是一個類,而是一個介面,我們有一個它的實現(這很常見)。要配置它,我們將在上面的 configure 方法中使用以下語法

@Override
protected void configure() {
    bind(NiceGreetingService.class).to(GreetingService.class);
}

這讀作“bind NiceGreetingService,並將其宣傳為 GreetingService”。這意味著我們可以在上面的 GreetingResource 中使用完全相同的程式碼,因為我們將合同宣傳為 GreetingService 而不是 NiceGreetingService。但注入時的實際實施將是 NiceGreetingService

那麼範圍呢?如果你曾經使用過任何注入框架,那麼你將會遇到範圍的概念,它決定了服務的生命週期。你可能聽說過請求範圍,其中服務僅在請求的生命週期記憶體在。或者是 Singleton Scope,其中只有一個服務例項。我們也可以使用以下語法配置這些範圍。

@Override
protected void configure() {
    bind(NiceGreetingService.class)
            .to(GreetingService.class)
            .in(RequestScoped.class);
}

預設範圍是 PerLookup,這意味著每次請求此服務時,都會建立一個新服務。在上面的示例中,使用 RequestScoped,將為單個請求建立新服務。這可能與 PerLookup 相同或不同,具體取決於我們嘗試注入的數量。我們可能會嘗試將其注入過濾器和資源。如果這是 PerLookup,那麼將為每個請求建立兩個例項。在這種情況下,我們只想要一個。

另外兩個可用的範圍是 Singleton(僅建立一個例項)和 Immediate(如 Singleton),但是在啟動時建立(而使用 Singleton,它不會在第一個請求之前建立)。

除了繫結類之外,我們還可以使用例項。這會給我們一個預設的單例,所以我們不需要使用 in 語法。

@Override
protected void configure() {
    bind(new NiceGreetingService())
            .to(GreetingService.class);
}

如果我們有一些複雜的建立邏輯或需要服務的某些請求上下文資訊,該怎麼辦?在這種情況下有 Factorys。我們可以注入我們的澤西資源的大多數東西,我們也可以注入一個 Factory。舉個例子

public class GreetingServiceFactory implements Factory<GreetingService> {
    
    @Context
    UriInfo uriInfo;
    
    @Override
    public GreetingService provide() {
        return new GreetingService(
                uriInfo.getQueryParameters().getFirst("name"));
    }
    
    @Override
    public void dispose(GreetingService service) {
        /* noop */
    }
}

這裡我們有一個工廠,它從 UriInfo 獲取請求資訊,在這種情況下是一個查詢引數,我們從中建立 GreetingService。要配置它,我們使用以下語法

@Override
protected void configure() {
    bindFactory(GreetingServiceFactory.class)
            .to(GreetingService.class)
            .in(RequestScoped.class);
}

而已。這些只是基礎知識。香港和澤西島要做的事情要多得多。