【问题标题】:Is it possible in CDI to inject a generic interface implementation?是否可以在 CDI 中注入通用接口实现?
【发布时间】:2016-03-03 00:48:43
【问题描述】:

我正在尝试创建类似于以下内容的类型转换器:

public interface ConvertToString<T> {
    String convert (T value);
}

public class ConvertLongToString implements ConvertToString<Long> {
    @Override
    public String convert (Long value) {
        return value.toString();
    }
}

public class Foo<T> {
    @Inject
    @Any
    private Instance<ConvertToString<T>> instance;

    private T value;

    public String getValue() {
        return instance.get().convert(value);
    }

    public void setValue(T value) {
        this.value = value;
    }
}

在本例中,我希望Foo&lt;Long&gt; 的实例知道如何通过注入正确的转换器来转换值。

这是否可以像我展示的那样使用 CDI,甚至通过迭代实例列表来实现?

我想构建类似于 Bean Validation 框架的东西,您可以在其中为许多类型创建相同注解的验证器(当然您有 @ValidatedBy...)

【问题讨论】:

标签: jakarta-ee cdi


【解决方案1】:

泛型仅在编译时使用,但话虽如此,您可以解析注入点并在运行时具有类似的行为。示例:

@Inject
@ConvertToString
ConvertToStringInterface<T> converter;

@Produces
@ConvertToString
public ConvertToStringInterface produceConverter(InjectionPoint injectionPoint) {
    Type type = injectionPoint.getType();
    ParameterizedType parameterizedType = (ParameterizedType) type;
    Type argType = parameterizedType.getActualTypeArguments()[0];

    Class<?> clazz = (Class<?>) argType;

    if (clazz == Long.class) {
        return new ConvertLongToString();
    }
}

这可能需要一些调整,但应该可以。

【讨论】:

  • 抱歉,您的解决方案不起作用,除非我遗漏了什么。在您的解决方案中,注入类型已经为您提供了参数化类型类。比如长。这显然与问题中展示的注入不对应。您的 {@code Type argType = parameterizedType.getActualTypeArguments()[0]; } 预计会从注入点返回如下内容: ConvertToStringInterface 转换器;长的?当注解在编译时有 时,它怎么会出现呢?而他的实例 {instance.get()} 也没有。
【解决方案2】:

正如我上面的评论所述,我认为被认为正确的答案根本不正确,即使它使用高级 CDI api 来回答你。看起来正确,但实际上,我认为它不可能正确。

另一方面,标有减号的答案是正确的,但过于抽象而无济于事。

他的制作人有一个问题。当 CDI 生成您的 bean 时,不可能知道具体实例返回到注入点。 它缺少 WELD 实施者在编译时缺少的相同信息来解决您的神秘问题。 因为注入点没有指定编译类型所需的具体参数化类型。 并且 instance.get() api 也没有任何帮助,因为它没有传递任何源类型。大功告成。

所以这个答案在技术上看起来不错,但实际上它不起作用。

我相信您可以通过多种方式解决问题。 我现在至少能想到两个,我相信还有其他的。

您可以创建一个愚蠢的外观转换器,它唯一知道如何为输入类型找出合适的转换器。 比如:

pulblic class MyClassThatNeedsAConverter{

@Inject
MyAllMightyFacadeConverterThatKnowsAllConcreteConverters converter;

public void someMethodThatNeedsToConverSomethin(T someTypeThatCanBeConverted){
// now i need to convert this to string
String convertedString = converter.convert(someTypeThatCanBeConverted);
// contiue with my business logic
}
}

那就是程序员 API。 然而,您的框架将实现这个强大的转换器外观。

public class MyAllMightyFacadeConverterThatKnowsAllConcreteConverters {

@Inject
ConvertToStringInterface<String> stringToStringConverter;
@Inject
ConvertToStringInterface<Long> longToStringConverter;
// and on you go with different concreted converters
// CDI can resolve these just fine, just make sure you do not have more than
// one candidate bean
...

public <T> String convert(T whateverTypeThatComesISwallow){
if(whateverTypeThatComesISwallow instaceof Long){
return longToStringConverter.convert((Long) whateverTypeThatComesISwallow ); 
} 

if(whateverTypeThatComesISwallow  instaceof SomeOtherType){
// same old story
}

throw new RuntimeException("You are out of luck, this type i do not know off an will not convert"  + whateverTypeThatComesISwallow.getClass().getCanonincalName() );
}
}

这是一条路线。

另一条路线是您可以拥有转换器缓存之类的东西。 所以你的框架可以提供类似的东西

@ApplicationScoped
public class MyConverterHolder{
@Inject
Instance<StringConverter<?>> allConverterInstancesKnownToTheSystem;

final Map<Class, StringConveter<?>> myCacheOfConveters = new HashMap<>();

@postConstruct
public postConstruct(){
 // you do not want to this initialization all the time
// so you just do it once during construction
    for(StringConveter<?> currentConverterInstance :     allConverterInstancesKnownToTheSystem) {
      myCacheOfConveters.put(currentConverterInstance .getSupportedInputType(), currentConverterInstance );
    }   
  } // post construct ends here

public <T>  String convert(T someTypedDataIcanSwallow){
Class<T> sourceType = someTypedDataIcanSwallow.getClass();
return myCacheOfConveters.get(sourceType).convert(someTypedDataIcanSwallow);
}

}

我更喜欢第二种方法。 当哈希映射可以为我完成这项工作并且我不必维护它时,我不喜欢使用 ifs 和 elses 发送垃圾邮件代码。 但重点是。 CDI 不能给你一个具体的实现来解决你的问题,除非你告诉它具体的类型。 如果你不给它具体的类型,CDI 可以给你所有符合某些类型标准的实例。但是接下来你必须解决挑选合适的问题。

所以你需要在转换之间创建这个外观对象。

关于参数化 tpye 注入的好读物当然是规范本身。 这描述了将 bean 解析为注入点的匹配规则,并在描述后给出了示例。 例如。请参阅“5.2.4. 原始类型和参数化类型的可分配性”部分。

 For example, Dao is eligible for injection to any injection point of
 type @Default Dao<Order>, @Default Dao<User>, @Default Dao<?>,
 @Default Dao<? extends Persistent> or @Default Dao<X extends
Persistent> where X is a type variable.

而且这一段也很重要:

合法 bean 类型中定义的任何合法 bean 类型都可以是 所需的注入点类型。此外,所需的类型 注入点可能包含通配符类型参数。然而,一个 类型变量不是合法的注入点类型。

如果注入点类型是类型变量,则容器 自动检测问题并将其视为定义错误。

亲切的问候。

【讨论】:

    【解决方案3】:

    泛型是一种编译时构造,不存在使其适用于运行时注入所需的信息,类型擦除只是打败了整个事情......天哪,我是否尝试过使用 Spring!

    【讨论】:

    • 是的,我想是这样的。只是不知道是否有某种魔法可以实现这一点。
    猜你喜欢
    • 2016-03-28
    • 2016-07-25
    • 1970-01-01
    • 2019-06-24
    • 2021-04-19
    • 1970-01-01
    • 1970-01-01
    • 2015-07-05
    • 2017-12-26
    相关资源
    最近更新 更多