【问题标题】:Java EE injection based on type parameters基于类型参数的 Java EE 注入
【发布时间】:2015-01-12 00:55:56
【问题描述】:

我正在尝试根据 ParameterizedType 选择一个 bean。与 Google Guice 相关的解决方案似乎有大量的答案,但 Java EE 7 是否有可能?

public interface Handler<T> { ... }

@ApplicationScoped StringHandler implements Handler<String> { ... }
@ApplicationScoped IntegerHandler implements Handler<Integer> { ... }

然后在其他一些托管 bean 中:

@Inject private Instance<Handler<?>> handlers;

public <T> Handler<T> handlerFor( Class<T> handledClass )
{
    // ???
}

下面是 Instance 中与类型相关的 select 方法:

<U extends T> Instance<U> select(TypeLiteral<U> subtype, Annotation... qualifiers);

TypeLiteral 的 javadoc 只是说你可以做类似的事情

 TypeLiteral<List<String>> stringListType = new TypeLiteral<List<String>>() {};

这非常无用,因为我不知道编译时的类型。但是,我确实将 Class 对象作为类型参数参数。

Guice 似乎可以通过为您构造 TypeLiteral 对象来处理这个问题。 Java EE 7 中没有类似的工具吗?

【问题讨论】:

  • 有什么理由不能做@Inject Handler&lt;T&gt; handler
  • @John 由于类型擦除,如果我使用类型参数,容器在运行时将不知道类型并且不知道要注入哪一个。我很想在运行时将 Class 传递给它,但是没有 select(...) 。
  • 您尝试注入的类是什么样的?

标签: java jakarta-ee generics cdi java-ee-7


【解决方案1】:

我不太明白你的问题。

但也许你可以在运行时通过

获得泛型类型
String typeName = handledClass.getClass().getTypeParameters()[0].getName();

然后为每个CDI Handler创建一个通用注解,并根据类型进行切换:

Annotation qualifier = typeName.equalsIgnoreCase("String") ? new AnnotationLiteral<StrHandler>() {} : new AnnotationLiteral<IntHandler> (){};

最后在您的实例中执行以下操作:

return handlers.select(qualifier).get();

编辑:有关基于 http://docs.jboss.org/weld/reference/latest/en-US/html_single/#_obtaining_a_contextual_instance_by_programmatic_lookup 上的 AnnotationLiteral 生成限定符注释的更多信息

别忘了在注射时使用@Any

   @Inject @Any Instance<Handler<?>> instance;

避免默认注入。

【讨论】:

  • 我认为最接近的方法是构建一个扩展,通过查看类型参数并在 bean 验证期间添加类型文字来自动注释我的处理程序类。不过,这仍然很痛苦,所有信息在运行时都可用,但我无法选择它。如果 Instance 类有一个“select(Type... typeParameters)”,那将是理想的,然后我可以传入一个类列表。
【解决方案2】:

聚会迟到了,但我遇到了类似的问题,想分享我的方法:

我没有使用Instance.select(它需要只能在编译时构造的TypeLiteral),而是简单地迭代所有实例(使用流),然后使用反射检查每个实例是否“适合”:

@Inject
@Any
Instance<Handler<?>> instances;

//...
Handler<?> handler = this.instances.stream()
    .filter(it -> this.fits(it, handledClass))
    .findAny().orElse(null);

fits 方法的实现有点尴尬——可以这么说(注意:根据 CDI 实现,它可能需要一些调整!)。此外,根据用例,如果处理程序还应该支持子类(相反,可以使用Class.isAssignableFrom),那么简单的相等性检查可能还不够!

private boolean fits(Class<?> clazz, Class<?> target)
{
    if (clazz == null)
      return false;
    
    Type[] genericInterfaces = clazz.getGenericInterfaces();
    
    boolean match = Arrays.stream(genericInterfaces)
        .filter(it -> it instanceof ParameterizedType)
        .map(it -> (ParameterizedType) it)
        .filter(it -> it.getRawType() == Handler.class)
        .anyMatch(it -> ((Class<?>) it.getActualTypeArguments()[0]) == target);
    
    if (match)
      return true;
    if (this.fits(clazz.getSuperclass(), target))
      return true;
    if (Arrays.stream(clazz.getInterfaces()).anyMatch(it -> this.fits(it, target)))
      return true;

    return false;
}

请注意,递归是必需的,因为 handler.getClass() 将返回 CDI 客户端代理的类,遗憾的是它没有以通用方式实现基类型的接口。此外,如果我们有以下构造,不幸的是FooHandlerImpl.class.getGenericInterfaces() 不会返回Handler&lt;Foo&gt;。所以我们需要自己遍历层次结构:

public interface FooHandler extends Handler<Foo> {  }
public class FooHandlerImpl implements FooHandler {  }

缓存结果可能是个好主意。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-10
    • 2020-07-04
    • 2018-08-28
    相关资源
    最近更新 更多