【问题标题】:CDI instantiate and inject parent-child type beans is either ambigious or child types is not foundCDI 实例化和注入父子类型 bean 不明确或未找到子类型
【发布时间】:2019-04-18 05:19:35
【问题描述】:

我有一个类似的界面:

public interface DateTimeService {
    ZonedDateTime now();

    void fixTo(ZonedDateTime date);

    void forget();
}

我有两个实现。一种用于fixToforget 抛出异常的生产环境,另一种用于测试我们控制时间的地方。然后我有一个 CDI 配置,它根据标志实例化正确的类型。

@ApplicationScoped
public class Configuration {
    @Produces
    @ApplicationScoped
    public DateTimeService dateTimeService(Configuration config) {
        if (config.isFakeDateTimeServiceEnabled()) {
            return new FakeDateTimeService();
        } else {
            return new DefaultDateTimeService();
        }
    }
}

但是,我想从DateTimeService 中删除fixToforget,因为它们只是在那里,所以我们可以控制测试时间。我制作了一个新界面,例如:

public interface FakeDateTimeService extends DateTimeService {
    // fixto and forget is moved from DateTimeService to here
}

我有几个注入点。有些在生产代码中,有些在测试代码中。在生产代码中我希望只能访问DateTimeSerice,在测试代码中我希望能够处理扩展服务。

在产品代码中:

    @Inject
    private DateTimeService dateTimeService;

在测试代码中:

    @Inject
    private FakeDateTimeService dateTimeService;

如果我保持配置不变,那么测试代码将永远找不到我的扩展服务(因为 CDI 似乎忽略了生产者方法生成的实例的运行时类型)。

如果我更新配置以实例化两者(在这种情况下,我什至可以将正版服务注入假服务),然后生产无法连接在一起,因为假的也实现了DateTimeService 接口,它会导致歧义。在这一点上,我可能只使用限定符,但我既不想因此而更改我的生产代码,也不想在生产环境中存在虚假的日期时间服务。

我试图否决 bean 创建,但我没有这样做。

我认为会/应该工作的是实例化正确的类型并以编程方式将其添加到 bean 上下文中,但我发现的示例是针对 CDI 2.0 而现在,我的代码的相关部分停留在 CDI 1.2 上。

此时您可能可以看出,我不是 CDI 专家。因此,我愿意就良好的 CDI 阅读材料以及有关此问题的具体建议提出建议。

否则,我将要放弃,只使用具有 fixToforget 方法的 DateTimeService

【问题讨论】:

  • CDI 使用类型安全解析,这里的重要一点是 bean 类型 - 您的 FakeDateTimeService 作为 bean 实际上具有类型 {FakeDateTimeService, DateTimeService, Object} (以及任何其他扩展类或实现的接口)。因此,它有资格注入这些类型的任何注入点,因此存在模棱两可的解析异常。如果我正确理解了您的意图,那么您可能想阅读 CDI Alternatives 并将 FakeDateTimeService 作为测试的启用替代品,因此在测试中的所有注入点替换 DateTimeService
  • 其他方法可能是有一个 CDI 扩展并否决原始类型并注册一个新 bean。对于 CDI 1.2,这会稍微复杂一些,而且扩展只需要用于测试。我宁愿寻找一种方法来使这项工作与替代品一起使用。
  • 绝对选择仅在测试类路径中可见的替代方案(或者可能是专家)。在生产中使用激活虚拟组件的开关让我感到害怕,因为一个错误可能会使生产不稳定或受到损害。如果你引用的测试代码只是单元测试,你甚至可以模拟DateTimeService
  • 谢谢你们。我明白为什么模棱两可,我只是不知道下一步该去哪里。我会看看两种建议的解决方案。祝你有美好的一天!
  • 您可能想看看ioc-unit。通过定义单独的测试代码和生产(sut)代码,很容易隐式定义 FakeDateTimeService 的优先级而无需替代

标签: java cdi


【解决方案1】:

根据您的操作方式,您可以使用以下方法,其中一些方法已在 cmets 中提及。

有替代品

对于测试,您提供一个自己的 beans.xml,它定义了测试的替代方案,它取代了生产的替代方案。您必须使它们保持同步,从而测试 beans.xml 包含测试所需的更改。根据 CDI 实现,beans.xml 可以省略,@Alternative 就足够了。

https://docs.oracle.com/javaee/6/tutorial/doc/gjsdf.html

interface DateTimeService  { ... }

// Active during production, beans.xml does not define alternative
class DefaultDateTimeService implements DateTimeService  { ... }

// Active during test, beans.xml defines alternative
@Alternative
class FakeDateTimeService implements DateTimeService { ... }

// Injection point stays the same.
@Inject
DateTimeService dateTimeService;

// Used in test environment
<beans ... >
    <alternatives>
        <class>FakeDateTimeService</class>
    </alternatives>
</beans>

与阿奎利安

使用 Aarquillian,您可以自己组装部署并排除 DefaultDateTimeService 实现并将其替换为 FakeDateTimeService 实现。使用这种方法,您不需要破解任何东西,因为在测试期间,只有 FakeDateTimeService 实现对 CDI 容器可见。

http://arquillian.org/guides/getting_started/

使用 CDI 扩展

对于测试,您可以在测试代码中提供 CDI 扩展和 FakeDateTimeService 实现,从而 CDI 扩展确实否决 DefaultDateTimeService 实现。

package test.extension

public class VetoExtension implements Extension {

    public <T> void disableBeans(@Observes ProcessAnnotatedType<T> pat) {
        // type matching
        if (DefaultDateTimeService.class.isAssignableFrom(pat.getAnnotatedType().getJavaClass())) {
            pat.veto();
        }
    }
}

// Define SPI provider file, assuming test/resources is on CDI classpath
src/test/resources/META-INF/services/javax.enterprise.inject.spi.Extension
// Single line you put in there
test.extension.VetoExtension 

Deltaspike @Exclude

Deltaspike 是一个 CDI 扩展库,它为 CDI 开发和测试提供了一些有用的实用程序,还提供了一个 CDITestRunner。 @Exclude 注解被一个扩展使用,它和上面的例子一样,但是已经为你实现了。

https://deltaspike.apache.org/

https://deltaspike.apache.org/documentation/test-control.html

https://deltaspike.apache.org/documentation/projectstage.html

您应该选择一种不会污染源代码并迫使您进行大量修改以使其运行以进行测试的方法。

我更喜欢 Arquillian,因为在这里您可以完全控制部署中的内容,并且您的生产代码中无需更改或专门实施,因此它是可测试的。

使用 Deltaspike,您可以排除用于测试的生产代码并将其替换为测试实现。

如果不能使用额外的库或框架,我会更喜欢另一种方法,因为它使用起来最简单。

【讨论】:

    猜你喜欢
    • 2023-03-10
    • 1970-01-01
    • 2021-01-25
    • 1970-01-01
    • 2010-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多