Java:方法链和依赖注入

Java: method-chaining and dependency injection

在使用由依赖注入框架(比如 HK2)管理的服务时,使用方法链是否可以接受?

我不确定是否允许 "cache" 实例,即使它只在注入范围内。

创建披萨的示例服务:

@Service
public class PizzaService {

    private boolean peperoni = false;
    private boolean cheese = false;
    private boolean bacon = false;

    public PizzaService withPeperoni() {
        peperoni = true;
        return this;
    }

    public PizzaService withCheese() {
        cheese = true;
        return this;
    }

    public PizzaService withBacon() {
        bacon = true;
        return this;
    }

    public Pizza bake() {
        // create the instance and return it
    }
}

此处服务被注入到 JAX-RS 资源中:

@Path('pizza')
public class PizzaResource {

    @Inject
    PizzaService pizzaService;

    @GET
    public Response getPizza() {
        Pizza pizza = pizzaService
            .withPeperoni()
            .withCheese()
            .bake();

        return Response.ok(pizza).build();
    }
}

您正在做的事情对该服务的所有其他用户都有副作用。它们都共享同一个服务实例,因此如果您调用 withPeperoni,它将为所有引用该服务的人更改该布尔值。

您似乎想要使用 Builder。也许您的服务可以实例化一个新的构建器,它将负责为您构建完美的比萨饼。这样你就可以避免所有可能的副作用:

@GET
public Response getPizza() {
    Pizza pizza = pizzaService.newPizzaBuilder()
        .withPeperoni()
        .withCheese()
        .bake();

    return Response.ok(pizza).build();
}

还有 PizzaBuilder:

public class PizzaBuilder {

    private boolean peperoni = false;
    private boolean cheese = false;
    private boolean bacon = false;

    public PizzaBuilder withPeperoni() {
        peperoni = true;
        return this;
    }

    public PizzaBuilder withCheese() {
        cheese = true;
        return this;
    }

    public PizzaBuilder withBacon() {
        bacon = true;
        return this;
    }

    public Pizza bake() {
        // create the instance and return it
    }
}

和比萨服务:

@Service
public class PizzaService {

    public PizzaBuilder newPizzaBuilder() {
        return new PizzaBuilder();
    }
}

这个解决方案并不完美,因为只实例化 Builder 的服务使用不多,但它至少可以防止您在解决方案中遇到的副作用。

这取决于 JAX-RS 资源的范围和服务的无状态性。

通常,每次请求时都会创建每个 JAX-RS 资源实例。

JSR339 3.1.1 生命周期和环境

By default a new resource class instance is created for each request to that resource. First the constructor (see Section 3.1.2) is called, then any requested dependencies are injected (see Section 3.2), then the appropriate method (see Section 3.3) is invoked and finally the object is made available for garbage collection.

对于以下 HTTP 请求,

GET /pizza HTTP/1.1

创建了 PizzaResource 的新实例,并在其中注入了 PizzaService 的可用实例。

现在您正在寻找的答案取决于 PizzaService 的无状态性和生命周期,这可能由容器维护。

希望我现在找不到规范,但是,即使 PizzaService@Stateless,容器也不会同时为不同的会话共享实例。

我会放置一个生命周期侦听器方法来重置服务。

@Path("/pizza")
public class PizzaResource {

    @PostConstruct
    private void resetPizzaService() { // invoked after the injection
        pizzaService.reset();
    }

    @Inject
    private PizzaService pizzaService;
}

reset() 的作用

public void reset() {
    peperoni = false;
    cheese = false;
    bacon = false;
}

更新

我刚刚为 @Service 找到了一个很好的线程,它似乎是 Spring 框架的一部分。 How does the singleton Bean serve the concurrent request?

基于@JinKwon 的 及其评论,这是我的解决方案:

  • 该服务标记为 @PerLookup,因为 @Singleton 是默认值。 (感谢@M.Deinum)
  • 根据源 class 的生命周期,我默认通过 Provider. This is not so much a problem in JAX-RS resources, since they are already @RequestScoped 注入服务。但是其他代码(后台进程、单元测试等)可能有不同的范围,并且 Provider 有所不同,每次都创建单独的新实例。

按照这种方法,我可以使用方法链接,返回 this。此外,这对我来说很重要,该实例由 DI 内核管理,并且可以访问依赖注入本身。

服务:

@Service
@PerLookup
public class PizzaService {

    Pizza pizza = new Pizza(); // naked pizza by default

    @Inject
    OvenService    oven; // just to show that I can use @Inject here

    public PizzaService withPeperoni() {
        pizza.peperoni = true;
        return this;
    }

    public PizzaService withCheese() {
        pizza.cheese = true;
        return this;
    }

    public PizzaService withBacon() {
        pizza.bacon = true;
        return this;
    }

    public Pizza bake() {
        return oven.bake(pizza);
    }
}

资源:

@Path('pizza')
public class PizzaResource {

    @Inject
    PizzaService pizzaService;

    @GET
    public Response getPizza() {
        Pizza pizza = pizzaService
            .withPeperoni()
            .withCheese()
            .bake();

        return Response.ok(pizza).build();
    }
}

Unittest(通过 javax.inject.Provider 注入的示例):

@HK2
@Test
public class PizzaServiceNGTest {

    @Inject
    PizzaService pizzaService;

    @Inject
    Provider<PizzaService> pizzaServiceProvider;

    public void testProviderInjection() {
        Pizza pizza;

        // pizza with the works
        pizza = pizzaServiceProvider.get()
            .withPeperoni()
            .withBacon()
            .withCheese()
            .bake();

        assertTrue(pizza.peperoni);
        assertTrue(pizza.bacon);
        assertTrue(pizza.cheese);

        // naked pizza
        pizza = pizzaServiceProvider.get()
            .bake();

        assertFalse(pizza.peperoni);
        assertFalse(pizza.bacon);
        assertFalse(pizza.cheese);
    }

    public void testDirectInjection() {
        Pizza pizza;

        // pizza with the works
        pizza = pizzaService
            .withPeperoni()
            .withBacon()
            .withCheese()
            .bake();

        assertTrue(pizza.peperoni);
        assertTrue(pizza.bacon);
        assertTrue(pizza.cheese);

        // naked pizza
        pizza = pizzaService
            .bake();

        // this is where it goes wrong: the pizzaService hasn't been reset and
        // is messing up the order!
        assertFalse(pizza.peperoni);    // will fail
        assertFalse(pizza.bacon);   // will fail
        assertFalse(pizza.cheese);  // will fail
    }

}