Java:方法链接和依赖注入

时间:2016-05-09 10:19:19

标签: java dependency-injection hk2

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

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

创建披萨的示例服务:

@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();
    }
}

3 个答案:

答案 0 :(得分:1)

您正在做的事情对该服务的所有其他用户都有副作用。它们都共享相同的服务实例,因此如果您调用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
    }
}

和PizzaService:

@Service
public class PizzaService {

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

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

答案 1 :(得分:1)

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

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

JSR339 3.1.1生命周期和环境

  

默认情况下,为每个对该资源的请求创建一个新的资源类实例。首先是构造函数(参见   调用3.1.2节,然后注入任何请求的依赖项(参见第3.2节),然后是相应的   调用方法(参见第3.3节),最后该对象可用于垃圾收集。

对于以下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?

答案 2 :(得分:0)

建立在@ JinKwon的answer及其评论之上,这是我的解决方案:

  • 该服务已标记为@PerLookup,因为@Singleton是默认设置。 (谢谢@M.Deinum)
  • 根据原始类的生命周期,我通过Provider注入服务。这在JAX-RS资源中并不是一个问题,因为默认情况下它们已经是@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
    }

}