Spring 如何在运行时查看类型参数?整个类型擦除怎么样?

问题描述 投票:0回答:2

我不明白怎么可能看到这样的异常消息

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.util.List<java.lang.String>' available

类型参数不是应该被运行时删除吗?像

<java.lang.String>
这样的东西如何能够在编译中幸存下来(众所周知,异常在运行时发生)? Spring拥有什么样的超能力?

这个问题是相似的,但是两个最高票数的答案都没有完全回答我的问题

  • GhostCat 再次罢工 表示类是通用的(不是其实际类型)这一事实被保留,可以被检索
  • yshavit说你可以通过反射获得超类的泛型类型
java spring generics type-erasure
2个回答
3
投票

正如在对 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。


1
投票

类型参数不是应该被运行时删除吗?

对于类实例来说这是正确的,但对于类本身则不然。类保留有关其类型参数以及字段和方法的信息。例如,在这段代码中

    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()BeanDefinitionResolvableType 显式提供类型信息。

最终,两端都有提供类型信息的方法,Spring 在连接 bean 提供者和消费者方面做得很好。

© www.soinside.com 2019 - 2024. All rights reserved.