CDI 扩展:我可以在两个作用域中公开一个接口吗?

发布于 2024-12-08 09:53:54 字数 1559 浏览 0 评论 0原文

我在单元测试框架中有一个接口:CDIMocker。我目前正在使用拦截器来允许在 CDI 容器中进行模拟。这是一个实验——我正在考虑的几种单元测试方法之一。 (另一个主要竞争者是对 CDI 之外的所有 bean 和单元测试使用构造函数和方法注入 - 在这种情况下,这项工作更像是 CDI 扩展中的学习练习)。

我有两个自定义范围 - TestClassScoped 和 TestMethodScoped。我的 JUnit4 自定义运行程序将适当的类和方法块包装在根据需要启动和停止这些作用域的语句中。如果需要,它还会启动 Weld-SE 的实例。它知道它是否在 CDI 中,因为扩展会记住。

无论我们在哪里使用,模拟接口都是相同的。最好在两个范围中都公开它,这样我就可以

 // Sadly Static Injection currently doesn't work, but if it did
 @Inject @TestClassScoped
 private static CdiMocker s_classScopedMocker

 @Inject @TestMethodScoped
 private CdiMocker m_methodScopedMocker

有其他明显的方法。我目前在 CDI 外部的单例上有一个工厂方法,它可以返回这些实例 (ThreadLocal) 中的任何一个,或创建一个新的短期实例。我还成功创建了两个具体类并在它们上声明了不同的范围。

我尝试过使用上面注释的生产者方法,但没有运气。也许是一个简单的错误,也许是一个误解。

@Produces @TestClassScoped
public CdiMocker getClassScopedMockerForCdi()
{
    return getTestClassContext().getMocker();
}

@Produces @TestMethodScoped
public CdiMocker getMethodScopedMockerForCdi()
{
    return getTestMethodContext().getMocker();
}

我认为从 CDI 文档的某些部分可以像我所做的那样在注入点上声明范围,但我注意到 Instance<>接口不允许我使用范围注释来 select() 所以这可能是错误的。

我可以提供两个限定符。注释可以同时是限定符和范围吗?

另一个想法是让我的扩展提供两个 Bean,两者都公开相同的类,但在不同的范围内。他们还可以提供自定义 create() 和 destroy(),因为 CdiMocker 实例由我的两个自定义上下文管理。我对 CDI 的印象是,一个给定的类只能存在于一个范围内,那么这是错误的吗?

关于什么是最好的有什么建议吗?

谢谢 - Richard

(我很想开源结果,但在工作时间里已经做了足够多的工作,所以我不得不问,所以不太可能。商业争论将是公开审查。我现在使用拦截器,缺点是它必须留在原处,但想知道我是否可以通过拦截扩展中的 bean 生命周期来实现某些目标。我们可以使用替代方案来实现与旧应用程序服务器通信的通信层等功能,但对于某些功能,单个单元测试需要自定义。模拟和替代品太全球化了。)

I have an interface in a unit testing framework: CDIMocker. I'm currently using an interceptor to allow mocking in a CDI container. It's an experiment - one of a couple of approaches to unit testing I'm considering. (The other main contender is to use constructor and method injection for all beans and unit test outside CDI - in which case this work becomes more a learning exercise in CDI Extensions).

I have two custom scopes - TestClassScoped and TestMethodScoped. My JUnit4 custom runner wraps the appropriate Class and Method blocks in statements that start and stop these scopes as needed. It also starts an instance of Weld-SE if needed. It knows if it's in CDI because the Extension remembers.

The mocker interface is the same wherever it us used. It would be nice to expose it in both scopes, so I could

 // Sadly Static Injection currently doesn't work, but if it did
 @Inject @TestClassScoped
 private static CdiMocker s_classScopedMocker

 @Inject @TestMethodScoped
 private CdiMocker m_methodScopedMocker

There are other obvious ways. I currently have a factory method on a singleton outside CDI that can return either of these instances (ThreadLocal), or create a new short lived one. I have also had success creating two concrete classes and declaring different scopes on them.

I've tried using Producer Methods annotated as above, but no luck. Perhaps a simple mistake, perhaps a misunderstanding.

@Produces @TestClassScoped
public CdiMocker getClassScopedMockerForCdi()
{
    return getTestClassContext().getMocker();
}

@Produces @TestMethodScoped
public CdiMocker getMethodScopedMockerForCdi()
{
    return getTestMethodContext().getMocker();
}

I thought from some part of the CDI documentation it was possible to declare scopes on injection points as I have done, but I note that the Instance<> interface does not allow me to select() using scoped annotation so maybe that is wrong.

I could provide two qualifiers. Can an annotation be a Qualifier and a Scope at the same time?

Another idea would be to have my extension provide two Bean<CdiMocker>, both exposing the same class but in different scopes. They could also provide custom create() and destroy() because the CdiMocker instances are managed by my two custom Contexts. The impression I get of CDI is that a given Class can only live in one Scope, so would this be Wrong?

Any suggestions on what is best?

Thanks
- Richard

(I'd love to open source the result, but have done enough in work time I'd have to ask so not likely. The business argument would be public review. I use an Interceptor now with the disadvantage that it has to be left in place, but wonder if I could achieve something by intercepting the bean lifecycle in the extension. We can use Alternatives for things like the comms layer that talks to our legacy app server, but for some things a single unit test wants a custom mock and Alternatives are too global.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

萧瑟寒风 2024-12-15 09:53:54

我已经创建了

@Qualifier
@Target({TYPE, METHOD, PARAMETER, FIELD})
@Retention(RUNTIME)
@Documented
public @interface Scoped
{
    Class<? extends Annotation> value();
}

当前有两个 Bean 实现。相关(不寻常)部分是:

/**
 * A Bean<> implementation for the CdiMocker beans
 */
class MockerBean implements Bean<CdiMocker>
{
    private final class ScopedAnnotation extends AnnotationLiteral<Scoped> implements Scoped
    {
        private static final long serialVersionUID = 1L;
        public Class<? extends Annotation> value() { return m_context.getScope(); }
    }

    private final CdiMockContextControl m_context;

    public MockerBean(CdiMockContextControl context)
    {
          m_context = context;
    }

bean 类是 CdiMocker.class

    @Override
    public Class<?> getBeanClass()
    {
          return CdiMocker.class;
    }

限定符包括我上面定义的 ScopedAnnotation。我还包括了“默认”和“任意”。也许我需要删除这些?

范围由我的 CdiMockContextControl 接口返回。

@Override
public Class<? extends Annotation> getScope()
{
    return m_context.getScope();
}

类型是我的 CdiMocker 接口,

@Override
public Set<Type> getTypes()
{
    Set<Type> types = new HashSet<Type>();
    types.add(CdiMocker.class);
    types.add(Object.class);
    return types;
}

因为生命周期是在其他地方管理的,所以我返回现有的接口。

@Override
public CdiMocker create(CreationalContext<CdiMocker> arg0)
{
    return m_context.getMocker();
}

...并且不要破坏它。

@Override
public void destroy(CdiMocker instance, CreationalContext<CdiMocker> ctx)
{
    // It is managed by the Context, so I must not destroy it here.
    ctx.release();
}

解决方案是使用限定符,所以我认为它现在是“正确的”。我想我可以通过这种方式使用生命周期管理吗?

我的测试类(我的 Runner 使用 CDI 实例化)有

/**
 * My CDI Extension makes a Mocking Context available in the Test Method Scope.
 * This will be created before every test method, then destroyed afterwards.
 */
@Inject @Scoped(TestMethodScoped.class)
private CdiMocker m_testMethodMocker;
  • Richard

I've created

@Qualifier
@Target({TYPE, METHOD, PARAMETER, FIELD})
@Retention(RUNTIME)
@Documented
public @interface Scoped
{
    Class<? extends Annotation> value();
}

I currently have two Bean implementations. Relevant (unusual) parts are:

/**
 * A Bean<> implementation for the CdiMocker beans
 */
class MockerBean implements Bean<CdiMocker>
{
    private final class ScopedAnnotation extends AnnotationLiteral<Scoped> implements Scoped
    {
        private static final long serialVersionUID = 1L;
        public Class<? extends Annotation> value() { return m_context.getScope(); }
    }

    private final CdiMockContextControl m_context;

    public MockerBean(CdiMockContextControl context)
    {
          m_context = context;
    }

The bean class is CdiMocker.class

    @Override
    public Class<?> getBeanClass()
    {
          return CdiMocker.class;
    }

The Qualifiers include my ScopedAnnotation defined above. I've also included Default and Any. Maybe I need to remove these?

The scope is returned by my CdiMockContextControl interface.

@Override
public Class<? extends Annotation> getScope()
{
    return m_context.getScope();
}

Type is my CdiMocker interface

@Override
public Set<Type> getTypes()
{
    Set<Type> types = new HashSet<Type>();
    types.add(CdiMocker.class);
    types.add(Object.class);
    return types;
}

Because the lifecycle is managed elsewhere I return the existing one.

@Override
public CdiMocker create(CreationalContext<CdiMocker> arg0)
{
    return m_context.getMocker();
}

... and don't destroy it.

@Override
public void destroy(CdiMocker instance, CreationalContext<CdiMocker> ctx)
{
    // It is managed by the Context, so I must not destroy it here.
    ctx.release();
}

The solution is using Qualifiers, so I suppose it is now "Correct". I assume I can use lifecycle management in this way?

My test class (which my Runner instantiates using CDI) has

/**
 * My CDI Extension makes a Mocking Context available in the Test Method Scope.
 * This will be created before every test method, then destroyed afterwards.
 */
@Inject @Scoped(TestMethodScoped.class)
private CdiMocker m_testMethodMocker;
  • Richard
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文