我不明白怎么可能看到这样的异常消息
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.util.List<java.lang.String>' available
类型参数不是应该被运行时删除吗?像
<java.lang.String>
这样的东西如何能够在编译中幸存下来(众所周知,异常在运行时发生)? Spring拥有什么样的超能力?
这个问题是相似的,但是两个最高票数的答案都没有完全回答我的问题
正如在对 Stack Overflow 问题的回答中所讨论的为什么在 Java 中没有删除成员字段的通用类型信息?,字段的通用类型信息在 Java 中是反射可用的。方法/构造函数参数也是如此。这解释了 Spring 如何知道需要特定的泛型类型。
此外,bean 定义通常是通过具体类或 bean 方法完成的。这两种情况都在编译时保留其类型信息。这解释了 Spring 如何知道 bean 的具体泛型类型是什么。
将这两者放在一起解释了当不存在与特定通用签名匹配的 bean 时 Spring 如何失败。
为了具体说明,我将举一个例子。假设我们有以下通用类:
public class GenericBean<T> {
}
这里有两个 bean 定义,一种是通过在子类上使用
@Service
注释来定义为 bean,另一种是在 @Bean
类中使用 Configuration
来定义:
@Service
public class GenericBeanService extends GenericBean<Integer> {
}
@Configuration
public class GenericBeanConfig {
@Bean
public GenericBean<String> genericBean() {
return new GenericBean<>();
}
}
在这两种情况下,这些 bean 的通用类型信息都可以在运行时使用反射获得。这意味着Spring可以使用反射来确定bean的具体泛型类型:
// GenericBean<String>
GenericBeanConfig.class.getMethod("genericBean").getGenericReturnType();
// GenericBean<Integer>
GenericBeanService.class.getGenericSuperclass();
这是一个使用通用 bean 的自动装配类:
@Service
public class AutowiredClass {
@Autowired private GenericBean<String> stringBean;
@Autowired private GenericBean<Integer> integerBean;
}
在这里,自动装配字段的通用类型信息也可以在运行时使用反射获得。这意味着Spring可以使用反射来确定bean的具体泛型类型:
// GenericBean<String>
AutowiredClass.class.getDeclaredField("stringBean").getGenericType()
// GenericBean<Integer>
AutowiredClass.class.getDeclaredField("integerBean").getGenericType()
由于 Spring 可以通过反射确定 Bean 的泛型类型以及自动装配属性的类型,因此它可以根据 Bean 的泛型正确分配 Bean。
类型参数不是应该被运行时删除吗?
对于类实例来说这是正确的,但对于类本身则不然。类保留有关其类型参数以及字段和方法的信息。例如,在这段代码中
void foo(){
List<String> list = new ArrayList<String>();
Class<?> listClass = list.getClass();
}
list
实例在运行时不知道其实际类型参数值String
,但其类ArrayList
(和接口List
)知道拥有类型参数,声明为E
或 T
(尽管实际上类型参数name可能仍会被删除,但只有边界很重要)。这是类型参数“投影”到实际类型String
的事实被删除,而不是类型参数本身存在的事实。
以同样的方式,声明的类字段和方法也保留它们的类型参数,无论是实际变量还是类型变量。在这种情况下更好,因为存储了实际参数值。所以,当你写这样的东西时
// inside Spring component
@Autowired
private List<String> listOfStrings;
Spring 能够检测到有一个名为
listOfStrings
的字段需要自动装配,并且它期望与 List<String>
兼容的内容。因此,Spring 完全能够确定消费端的期望。
在另一端,您通常使用
@Bean
或 @Component
注释(或派生注释)注册 Bean。同样,这些附加到方法或类,do保留其类型信息。
即使以编程方式添加 Bean,仍然可以选择通过 GenericApplicationContext.registerBean()、BeanDefinition 和 ResolvableType 显式提供类型信息。
最终,两端都有提供类型信息的方法,Spring 在连接 bean 提供者和消费者方面做得很好。