美文网首页
spring 泛型处理

spring 泛型处理

作者: 但莫 | 来源:发表于2020-06-27 13:19 被阅读0次

    java 泛型基础

    • 泛型类型:泛型类型是在类型上参数化的泛型类或接口

    • 泛型使用场景

      • 编译时前类型检查。定义为 Collection<String> 类型的集合,add int时会有编译错误。
      • 避免类型强转。如果只是定义 Collection,我们是不知道集合保存的是什么类型的,即便知道,在get之后也需要强制转换成需要的类型。
      • 实现通用算法。如通用的二分查找,排序。
    • 泛型类型擦写

      • java引入java,一遍在编译时提供更严格的类型检查并支持泛型变成。类型擦除是为了不会为参数化类型创建新的类。因此,泛型不会产生运行时开销。而CGLIB这种动态代理技术,会在运行时修改字节码,会产生一些运行时的消耗。为了实现泛型,编译器将类型擦除应用于:
        • 将泛型类型中的所有类型参数替换为其边界,如果类型参数无边界,则将其替换为 Object。因此生成的字节码只包含普通类、接口和方法
        • 必要时插入类型转换以保持类型安全
        • 生成桥接方法以保留泛型类型中的多态性。

    以最常用的Collection为例看下泛型的例子

    Collection<String> list = new ArrayList<>();
    list.add("hello");
    // 编译错误
    //    list.add(1);
    // 泛型擦除
    Collection temp = list;
    // 编译通过
    temp.add(1);
    System.out.println(list);
    // ClassCastException
    list.forEach(System.out::println);
    

    从字节码看泛型

    Java 泛型是在编译时实现的,jvm是没有泛型的概念的,也就是说泛型是java语言的特性。既然 jvm 没有泛型的特性,要让编译后的 .class 支持 Java 语言的泛型,字节码就是二者之间的桥梁。

    public class Generic<T> {
    
        private T data;
    
        public T get() {
            return data;
        }
    
        public void set(T data) {
            this.data = data;
        }
    }
    

    从生成的字节码中可以看到,泛型 T 已经被擦除了:

    <init>()V
    getData()Ljava/lang/Object;
    setData(Ljava/lang/Object;)V
    

    类型擦除与多态

    GenericB 继承 Generic 并重写 getData、setData,从 Generic 的字节码知道参数类型是 Object,而 GenericB 定义的是 Number,正常应该是不能重写的,但是编译和运行时没有错误的。jvm为了解决这个问题,引入了桥接方法

    public class GenericB extends Generic<Number> {
    
      private Number n;
    
      @Override
      public Number getData() {
        return this.n;
      }
    
      @Override
      public void setData(Number data) {
        this.n = data;
      }
    }
    

    字节码如下,我只举例 getData 方法。编译器自动生成了“java.lang.Object getData()”方法,在方法里调用“java.lang.Number getData()”方法。

    byte code
      public java.lang.Number getData();
        descriptor: ()Ljava/lang/Number;
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: getfield      #2                  // Field n:Ljava/lang/Number;
             4: areturn
          LineNumberTable:
            line 9: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Lgeneric/GenericB;
    
      public java.lang.Object getData();
        descriptor: ()Ljava/lang/Object;
        flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokevirtual #5                  // Method getData:()Ljava/lang/Number;
             4: areturn
          LineNumberTable:
            line 3: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Lgeneric/GenericB;
    
    

    再来看下没有写泛型的继承是如何实现的,GenericC继承自 Generic ,但是没有指定泛型类型。

    public class GenericC extends Generic {
    
        private Number n;
    
        @Override
        public Number getData() {
            return this.n;
        }
    
        public void setData(Number data) {
            this.n = data;
        }
    }
    

    生成的字节码如下。父类中的方法为 getData()Object 和 setData(Object),GenericC 类中的 setData(Number) 与父类 set(Object) 方法参数不同,所以是是重载。我们知道,只有返回值不同不满足重载条件,所以对 GenericC 类的 getData()Number 方法来说,应该算是对父类方法 getData()T 的重写。
    编译期自动生成了 getDate() Object 桥接方法来重写父类方法。我们发现字节码里存在了两个只有返回值类型不同的同名方法,不符合java语法。这是因为java和jvm方法签名定义不同:

    • Java 方法签名 = 方法名 + 参数类型 + 参数顺序
    • JVM 方法签名 = 方法名 + 参数类型 + 参数顺序 + 返回值类型 + 可能抛出的异常

    在java反射的api里,Method有一个方法是 isBridge 就是用来判断是否桥接方法的。

    {
      private java.lang.Number n;
        descriptor: Ljava/lang/Number;
        flags: ACC_PRIVATE
    
      public generic.GenericC();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method generic/Generic."<init>":()V
             4: return
          LineNumberTable:
            line 3: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Lgeneric/GenericC;
    
      public java.lang.Number getData();
        descriptor: ()Ljava/lang/Number;
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: getfield      #2                  // Field n:Ljava/lang/Number;
             4: areturn
          LineNumberTable:
            line 9: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Lgeneric/GenericC;
    
      public void setData(java.lang.Number);
        descriptor: (Ljava/lang/Number;)V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=2, args_size=2
             0: aload_0
             1: aload_1
             2: putfield      #2                  // Field n:Ljava/lang/Number;
             5: return
          LineNumberTable:
            line 13: 0
            line 14: 5
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       6     0  this   Lgeneric/GenericC;
                0       6     1  data   Ljava/lang/Number;
    
      public java.lang.Object getData();
        descriptor: ()Ljava/lang/Object;
        flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokevirtual #3                  // Method getData:()Ljava/lang/Number;
             4: areturn
          LineNumberTable:
            line 3: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Lgeneric/GenericC;
    }
    

    java 5 类型接口

    • java 5 类型接口 - java.lang.reflect.Type
    类或接口 说明
    java.lang.Class Java 类API,如java.lang.Integer
    java.lang.reflect.GenericArrayType 泛型数组类型,比如 T[]
    java.lang.reflect.ParameterizedType 泛型参数类型
    java.lang.reflect.TypeVariable 泛型类型变量,如Collection<E> 中的E
    java.lang.reflect.WildcardType 泛型通配类型
    • java 泛型反射API
    类型 API
    泛型信息 Generics Info java.lang.Class#getGenericInfo()
    泛型参数 Parameters java.lang.reflect.ParameterizedType
    泛型父类 super classes java.lang.Class#getGenericSuperclasses()
    泛型接口 interfaces java.lang.Class#getGenericInterfaces()
    泛型生命 generics Declaration java.lang.reflect.GenericDeclaration
    // 原生类型 primitive types : int long float
    Class intClass = int.class;
    System.out.println("intClass = " + intClass);
    
    // 数组类型 array types : int[],Object[]
    Class objectArrayClass = Object[].class;
    System.out.println("objectArrayClass = " + objectArrayClass);
    
    // 原始类型 raw types : java.lang.Integer
    Class IntegerRawClass = Integer.class;
    System.out.println("IntegerRawClass = " + IntegerRawClass);
    
    System.out.println("ArrayList.class = " + ArrayList.class);
    // 泛型参数类型 parameterized type
    ParameterizedType arrayListParameterizedType = (ParameterizedType) ArrayList.class
        .getGenericSuperclass();
    System.out.println("ArrayListParameterizedType = " + arrayListParameterizedType);
    System.out.println(
        "arrayListParameterizedType.getRawType() = " + arrayListParameterizedType.getRawType());
    
    System.out.println("泛型类型变量 Type Variable");
    // <E>
    Type[] typeVariables = arrayListParameterizedType.getActualTypeArguments();
    Stream.of(typeVariables)
        .map(TypeVariable.class::cast) // Type -> TypeVariable
        .forEach(System.out::println);
    

    spring 4.2 泛型优化实现与局限性- ResolvableType

    • 核心 API org.springframework.core.ResolvableType
      • 工厂方法:forXX 方法
      • 转换方法:asXX 方法
      • 处理方法:resolveXX 方法
      • GenericTypeResolver, GenericCollectionTypeResolver 替代者

    ResolvableType 是一个 immutable 设计,通过forXX, asXX 返回一个新的 ResolvableType 实例,然后通过 resolveXX,getXX 等方法获取类型信息。

    ResolvableType 设计的优点

    • 简化Type API 开发;
    • 不变设计,线程安全
    • Fluent API 设计,builder模式,链式编程。

    demo:

    private HashMap<Integer, List<String>> myMap;
    
    public static void main(String[] args) throws NoSuchFieldException {
    
        ResolvableType t = ResolvableType.forField(ResolvableTypeDemo.class.getDeclaredField("myMap"));
        t.getSuperType(); // AbstractMap<Integer, List<String>>
        t.asMap(); // Map<Integer, List<String>>
        t.getGeneric(0).resolve(); // Integer
        t.getGeneric(1).resolve(); // List
        t.getGeneric(1); // List<String>
        t.resolveGeneric(1, 0); // String
        t.resolveGeneric(1, 1); // null
    }    
    

    一个案例

    举一个例子,我们在定义一个支持泛型接口时,可以用不同的泛型类实现,当注入的时候指定具体的泛型类,spring 就会把具体类型的实现注入到 bean 中,这个过程就涉及到泛型的处理。代码如下:

    spring 版本 5.2.7。

    
    /**
     * 泛型注入 demo
     */
    public class GenericIocDemo {
      @Bean
      public UserMonitor userMonitor() {
        return new UserMonitor();
      }
    
      @Bean
      public PersonMonitor personMonitor() {
        return new PersonMonitor();
      }
    
      private List<Consumer<User>> users;
    
      @Autowired
      public void setUsers(List<Consumer<User>> users) {
        this.users = users;
      }
    
      @PostConstruct
      public void init() {
        // 注入的对象是 userMonitor
        users.forEach(System.out::println);
      }
    
      public static void main(String[] args) {
    
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(GenericIocDemo.class);
        context.refresh();
        context.close();
      }
    
      public static class UserMonitor implements Consumer<User> {
        @Override
        public void accept(User user) {
        }
      }
    
      public static class PersonMonitor implements Consumer<Person> {
        @Override
        public void accept(Person user) {
        }
      }
    }
    

    demo 中注入 private List<Consumer<User>> users,只会注入 UserMonitor 类型的 bean,这个过程涉及到一定涉及到泛型的判断,判断都是通过 ResolvableType 实现的,可以一窥 spring 的使用场景。下面是 spring 处理的主要流程。

    image
    通过 descriptorResolvableType 只获取了集合泛型,并没有获取泛型的泛型。这是为了在下一步获取候选 bean 做准备。因为 spring 不能通过泛型类型获取 bean,所以这里获取到 Consumer即可 image

    因为 requiredType 是 Consumer 接口,所以获取到两个候选 bean。

    image

    查看这部分代码的时候参照调用栈。第一行获取 实际的泛型类型,方法的最后会用来判断候选对象是否匹配(包括泛型)。

    image

    在比较之前,会获取候选 bean 的类型。descriptor.getDependencyType() 获取没有泛型的类型,然后和实际类型比较是否匹配,如果匹配则返回。

    image
    1. 比较类型是否匹配,包括泛型
    2. 因为泛型不匹配。所以结果是false,不会添加注入的集合中。

    总结

    简单介绍了java语言和jvm对泛型的支持方式。

    java 的泛型擦写是在运行时。其实从字节码的角度看并不存在擦写,因为编译后全部都是Object,擦写描述的其实是java代码编写的不规范,欺骗了编译器,从而导致运行时错误。

    spring框架封装的 ResolvableType 实现原理。ResolvableType 可以在我们的项目中直接使用,api设计的也很友好。

    使用框架,了解原理,事半功倍。

    参考

    相关文章

      网友评论

          本文标题:spring 泛型处理

          本文链接:https://www.haomeiwen.com/subject/rveoxktx.html