Spring如何能在运行时看到类型参数?类型擦除会怎么样?

huangapple go评论85阅读模式
英文:

How is Spring able to see type arguments at runtime? What about the whole type erasure thing?

问题

我不明白如何看到异常消息像这样的情况

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 表示您可以通过反射获得父类的泛型类型
英文:

I don't understand how it is possible to see exception messages like this one

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type &#39;java.util.List&lt;java.lang.String&gt;&#39; available

Aren't type arguments supposed to be erased by runtime? How can things like &lt;java.lang.String&gt; survive compilation (as we know, exceptions occur during runtime)? What kind of supernatural ability does Spring possess?

This question is similar, but none of the two most-upvoted answers answer my question exactly

  • GhostCat again on strike says the fact that a class is generic (not what its actual type is) is preserved, can be retrieved
  • yshavit says you can obtain a superclass's generic type through reflexion

答案1

得分: 3

根据Stack Overflow上的讨论,Java中字段的泛型类型信息是通过反射可用的,这也适用于方法/构造函数的参数。这解释了Spring如何知道特定泛型类型是必需的。

另外,Bean的定义通常通过具体类或Bean方法完成。这两种情况都在编译时保留其类型信息。这解释了Spring如何知道Bean的具体泛型类型是什么。

将这两者结合起来,就能解释Spring如何在没有与特定泛型签名匹配的Bean时失败。

示例

为了更具体,我将给出一个示例。假设我们有以下泛型类:

public class GenericBean<T> {
}

这里有两个Bean定义,一个是通过在子类上使用@Service注解定义的,另一个是在Configuration类中使用@Bean定义的:

@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。

英文:

As discussed in a answer to Stack Overflow question https://stackoverflow.com/q/65651242/1108305, generic type information of fields is reflectively available in Java. This is also true of method/constructor parameters. This explains how Spring can know that a particular generic type is required.

Additionally, bean definitions are often done via concrete classes or bean methods. Both of these cases retain their type information at compile time. This explains how Spring can know what the specific generic type of a bean is.

Putting these two together explains how Spring is able to fail when no bean matching a specific generic signature exists.

Example

To make it concrete, I'm going to give an example. Let's say we have the following generic class:

public class GenericBean&lt;T&gt; {
}

Here are two bean definitions, one which is defined as a bean by using the @Service annotation on a subclass, and one by using @Bean within a Configuration class:

@Service
public class GenericBeanService extends GenericBean&lt;Integer&gt; {
}
@Configuration
public class GenericBeanConfig {
    @Bean
    public GenericBean&lt;String&gt; genericBean() {
        return new GenericBean&lt;&gt;();
    }
}

In both of these cases, the generic type information of these beans is available at runtime using reflection. This means that Spring can use reflection to determine the specific generic types of the beans:

// GenericBean&lt;String&gt;
GenericBeanConfig.class.getMethod(&quot;genericBean&quot;).getGenericReturnType();

// GenericBean&lt;Integer&gt;
GenericBeanService.class.getGenericSuperclass();

Here is an autowired class that uses the generic beans:

@Service
public class AutowiredClass {
    @Autowired private GenericBean&lt;String&gt; stringBean;
    @Autowired private GenericBean&lt;Integer&gt; integerBean;
}

Here too, the generic type information of the autowired fields is available at runtime using reflection. This means that Spring can use reflection to determine the specific generic types of the beans:

// GenericBean&lt;String&gt;
AutowiredClass.class.getDeclaredField(&quot;stringBean&quot;).getGenericType()

// GenericBean&lt;Integer&gt;
AutowiredClass.class.getDeclaredField(&quot;integerBean&quot;).getGenericType()

Since Spring can determine via reflection the generic types of the beans, and the types of the autowired properties, it can therefore correctly assign beans based on their generics.

答案2

得分: 1

在运行时,类型参数不应该被擦除吗?

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

void foo(){
  List<String> list = new ArrayList<String>();
  Class<?> listClass = list.getClass();
}

list 实例在运行时不知道其实际的类型参数值 String,但它的类 ArrayList(以及接口 List)确实知道它拥有一个类型参数,声明为 ET(尽管实际类型参数的名称可能仍然被擦除,只有边界才重要)。它的类型参数“投影”到实际类型 String 的事实被擦除了,但类型参数本身的存在并没有被擦除。

以相同的方式,声明的类字段和方法也保留其类型参数,无论是实际参数值还是类型变量。在这种情况下,情况甚至更好,因为实际参数值被存储了下来。所以,当你写类似这样的代码时:

// 在Spring组件内部
@Autowired
private List<String> listOfStrings;

Spring 能够检测到有一个名为 listOfStrings 的字段需要自动装配,并且它期望与 List<String> 兼容的内容。因此,Spring 完全能够确定在消费端期望什么。

另一方面,通常使用 @Bean@Component 注解(或派生注解)注册 bean。而且再次强调,这些注解附加到保留其类型信息的方法或类上。

即使以编程方式添加 bean,仍然可以通过 GenericApplicationContext.registerBean()BeanDefinitionResolvableType 显式提供类型信息的选项。

最终,在两个端点都有提供类型信息的方法,并且 Spring 在连接 bean 提供程序和消费者方面做得很出色。

英文:

> Aren't type arguments supposed to be erased by runtime?

It is correct for class instances, but not for classes themselves. Classes keep information about their type parameters as well as their fields and methods. For example, in this code

    void foo(){
      List&lt;String&gt; list = new ArrayList&lt;String&gt;();
      Class&lt;?&gt; listClass = list.getClass();
    }

list instance is not aware at runtime of its actual type parameter value String, but its class ArrayList (and interface List) is aware of owning a type parameter, declared as E or T (though actually type parameter name might still be erased, only bounds matter). It's fact of "projection" of type parameter into actual type String is erased, not the fact of existence of type param itself.

In same manner, declared class fields and methods also keep their type parameters, either actual or type variables. Even better in this case, as actual parameter values are stored. So, when you write something like this

    // inside Spring component
    @Autowired
    private List&lt;String&gt; listOfStrings;

Spring is able to detect that there is a field named listOfStrings that needs to be autowired and it expects something compatible with List&lt;String&gt;. So, Spring is totally able to determine what is expected on the consuming end.

On the other end, you normally register beans with @Bean or @Component annotations (or derived). And again, these are attached to methods or classes that do keep their type information.

And even when beans are added programmatically, there are still options to provide type information explicitly through GenericApplicationContext.registerBean(), BeanDefinition and ResolvableType.

Ultimately, on both ends there are ways to provide type information, and Spring does great job to connecting bean providers and consumers.

huangapple
  • 本文由 发表于 2023年7月17日 12:39:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/76701522.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定