CDI实例化并注入父子类型的bean含糊不清或未找到子类型

时间:2019-04-18 05:19:36

标签: java cdi

我有一个类似的界面

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

但是,我想从fixTo中删除forgetDateTimeService,因为它们仅在此处,因此我们可以控制测试时间。我做了一个新的界面,如:

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

我有几个注射点。有些在生产代码中,有些在测试代码中。在生产代码中,我只希望访问DateTimeSerice;在测试代码中,我希望能够获得扩展服务的句柄。

在产品代码中:

    @Inject
    private DateTimeService dateTimeService;

在测试代码中:

    @Inject
    private FakeDateTimeService dateTimeService;

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

如果我更新配置以实例化这两个配置(在这种情况下,我什至可以将真正的服务注入到伪造的服务中),那么由于伪造品还实现了DateTimeService接口,因此生产无法连接在一起,这会导致模棱两可。在这一点上,我可能只使用限定符,但是我既不想更改生产代码,也不必在生产环境中使用伪造的日期时间服务。

我试图否决Bean的创建,但失败了。

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

在这一点上,您可能知道我不是CDI专家。因此,我愿意就好的CDI阅读材料提出建议,并就此问题提出具体建议。

否则,我将放弃并只使用拥有DateTimeServicefixTo方法的forget

1 个答案:

答案 0 :(得分:0)

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

有其他选择

为进行测试,您提供了一个自己的beans.xml,该文件定义了替代测试的替代产品,替代了生产版本。您将必须使其保持同步,从而测试bean.xml包含测试必需的更改。根据CDI的实现,可以省略bean.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>

和Arquillian一起

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

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,您可以排除用于测试的生产代码,并将其替换为测试实现。

如果没有额外的库或框架可以使用,我会选择另一种方法,因为它使用起来最简单。