使用 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);
}

而已。这些只是基础知识。香港和泽西岛要做的事情要多得多。