为任何T自动装配Class<T>

5

所以,我有一些通用组件,它们使用反射来初始化自己,并且在这样做时,需要在实例化时提供Class<T>对象。这些组件使用注释来生成有用的元数据和/或将对象转换为更适合手头任务的另一种表示形式。

我将我的问题简化为此示例组件:

@Component
public class Instantiator<T> {
    final Class<T> klass;

    @Autowired
    public Instantiator(Class<T> klass) {
        this.klass = klass;
    }

    public T instantiate() {
        try {
            return klass.newInstance();
        } catch (InstantiationException|IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}

Spring不知道如何自动注入Class<T>实例,因此为了每个我想要Class<T>可用的T编写了以下样板代码。

@Bean
Class<Instantiatee> instantiateeClass() {
    return Instanciatee.class;
}

它不起作用。

自Spring 4版本以来,支持对泛型类型进行自动装配,但在我的情况下,必须推断出T分配给了Class<T>。由于默认情况下Spring创建单例bean,因此无法推断出适当的T,我尝试添加@Scope("prototype"),但最终出现了ClassCastException,因为容器不知道如何推断T

因此,我从Instantiator中删除了@Component注释,并为我拥有的每个T解决了这个问题:

@Bean
Instantiator<Instantiatee> instantiator() {
    return new Instantiator<>(Instantiatee.class);
}

你是否了解一种解决办法,以便每次我想要一个依赖于TInstantiator或另一个泛型组件时都可以推断出它?
FYI,我们正在使用spring 4.1.4和boot。
我在这里发布了更完整的示例:https://gist.github.com/anonymous/79e1a7ebe7c25c00a6c2

不清楚你想要实现什么。你将在哪里以及如何使用这个单例的 Instanciator bean?请发布完整的 NoSuchBeanDefinitionException 堆栈跟踪,我无法重现它(但我也不知道你尝试了什么)。 - Sotirios Delimanolis
顺便提一下,英语动词的拼写是 "instantiate",而不是 "instanciate"(您的代码可能是在不同的语言中,但您也在问题中使用它)。 - Erwin Bolwidt
整个设计听起来有问题,但我认为你需要限定词才能使其正常工作。 - chrylis -cautiouslyoptimistic-
1个回答

0

使用@Bean定义bean时,默认情况下在方法名中给出bean名称 - 在您的情况下是getInstanciateeClass。此外,在自动装配默认bean名称被认为是参数名称,在您的情况下是klass。由于这个原因,Spring不能匹配bean,因为它们具有不同的名称,很可能存在多个Class实例在ApplicationContext中。无论一个是Class<Foo>,另一个是Class<Bar>,Spring都将它们视为Class,因此无法按类型进行自动装配。

您可以通过在定义bean和自动装配时使用相同的默认名称来解决此问题。

@Autowired
public Instanciator(Class<T> klass) {
    this.klass = klass;
}

@Bean
Class<Instanciatee> klass() {
    return Instanciatee.class;
}

你也可以在 @Bean 注解中指定 bean 的名称:

@Autowired
public Instanciator(Class<T> klass) {
    this.klass = klass;
}

@Bean(name = "klass")
Class<Instanciatee> getInstanciateeClass() {
    return Instanciatee.class;
}

或者在自动装配时也可以给出bean名称:

@Autowired
@Qualifier("getInstanciateeClass")
public Instanciator(Class<T> klass) {
    this.klass = klass;
}

@Bean
Class<Instanciatee> getInstanciateeClass() {
    return Instanciatee.class;
}

这意味着我原本想要的设计,即“T”可以更改为(Instanciator<A>,Instanciator<B>...)无法通过Spring实现。 我想我将不得不选择一种工厂模式。谢谢! - madidier
我发现你的答案在Spring 4.1版本中是不准确的:有一些支持泛型组件的方法。问题在于Instanciator类在Class<T>中使用了类型参数T。当请求Instanciator<FooBar>时,bean工厂不知道T等同于FooBar,因此它可能会像解释通配符一样解释它,并选择任何Class对象,无论其实际完整类型如何。 - madidier

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接