Spring杂谈 | 你真的了解泛型吗?从java的Type到Spring的ResolvableType(2)

简介: Spring杂谈 | 你真的了解泛型吗?从java的Type到Spring的ResolvableType(2)

ResolvableType


在学习了Java的Type体系后,我们会发现,依赖于整个Type体系去处理泛型代码非常的繁琐,并且不易于理解。基于这种情况,Spring开发了一个ResolvableType类,这个类对整个Type体系做了系统的封装。


实际上关于ResolvableType的学习大家可以参数Spring中的org.springframework.core.ResolvableTypeTests类,这是作者写好的单元测试类,覆盖了ResolvableType的所有方法。


这个类的代码量很大,不过我们也没有必要去详细地看每一行代码,粗略阅读源码后会发现这个类有以下几个特点


概览


1.所有的构造函数都是私有的

微信图片_20221112215002.png

在上图中那把小锁代表权限为private,就是私有的意思

构造函数都是在为相同的成员变量赋值,这里我随便放一个构造函数如下

private ResolvableType(Type type, @Nullable TypeProvider typeProvider,
                       @Nullable VariableResolver variableResolver, @Nullable ResolvableType componentType) {
    this.type = type;
    this.typeProvider = typeProvider;
    this.variableResolver = variableResolver;
    this.componentType = componentType;
    this.hash = null;
    this.resolved = resolveClass();
}

因为构造函数是私有的,所有它提供了一系列的方法来创建一个ResolvableType,如下:

微信图片_20221112215125.png

所有for开头的方法都是静态方法,同时都能获取一个ResolvableType,现在对常见的几个方法进行分析:


方法分析


forClass系列方法


Spring中经常会用到一个方法,ResolvableType.forRawClass(type),我们就先看下这一系列的三个方法


  1. ResolvableType.forRawClass(type)
  2. ResolvableType forClass(@Nullable Class<?> clazz)
  3. ResolvableType forClass(Class<?> baseType, Class<?> implementationClass)
  4. ResolvableType forClassWithGenerics(Class<?> clazz, Class<?>… generics)
  5. ResolvableType forClassWithGenerics(Class<?> clazz, ResolvableType… generics)


forRawClass(Class<?> clazz)

public static ResolvableType forRawClass(@Nullable Class<?> clazz) {
    return new ResolvableType(clazz) {
        @Override
        public ResolvableType[] getGenerics() {
            return EMPTY_TYPES_ARRAY;
        }
        @Override
        public boolean isAssignableFrom(Class<?> other) {
            return (clazz == null || ClassUtils.isAssignable(clazz, other));
        }
        @Override
        public boolean isAssignableFrom(ResolvableType other) {
            Class<?> otherClass = other.getRawClass();
            return (otherClass != null && (clazz == null || ClassUtils.isAssignable(clazz, otherClass)));
        }
    };
}

这个方法实际上做了两件事

1.用了构造函数,private ResolvableType(@Nullable Class<?> clazz)

2.复写了三个方法

对比另外一个方法


forClass(Class<?> clazz)

public static ResolvableType forClass(@Nullable Class<?> clazz) {
    return new ResolvableType(clazz);
}

对比后可以发现,这两个方法唯一的区别就是没有复写其中的三个方法。大家可以思考下,这是为什么呢?


其实区别在于,对于第一个forRawClass方法,入参传入的一定是一个原始数据类型,也就是一个不带泛型的类的Class对象,比如传入的可能是一个Person.class,Dog.class。对于这种原始数据类型,其getGenerics,isAssignableFrom方法的实现逻辑是固定的,所以forRawClass方法直接对这三个方法进行了复写。


forClass(Class<?> baseType, Class<?> implementationClass)

public static ResolvableType forClass(Class<?> baseType, Class<?> implementationClass) {
    Assert.notNull(baseType, "Base type must not be null");
    // as方法在之后分析,就是根据继承链找打对应的父类
    ResolvableType asType = forType(implementationClass).as(baseType);
    return (asType == NONE ? forType(baseType) : asType);
}

implementationClass是baseType的子类,这个方法主要获取baseType上定义的泛型,例如:


public class ResolvableTypeDemo {
    public static void main(String[] args) {
    // 获取到C继承的HashMap所构建的一个ResolvableType,会带用泛型<String, Integer>
        ResolvableType resolvableType = ResolvableType.forClass(HashMap.class, C.class);
        ResolvableType[] generics = resolvableType.getGenerics();
        for (ResolvableType generic : generics) {
            // 程序打印:
            // class java.lang.String
            // class java.lang.Integer
            System.out.println(generic.getType());
        }
    }
}
class C extends HashMap<String, Integer> {
}

forConstructor系列方法

public static ResolvableType forConstructorParameter(Constructor<?> constructor, int parameterIndex,
                                                     Class<?> implementationClass) {
    Assert.notNull(constructor, "Constructor must not be null");
    MethodParameter methodParameter = new MethodParameter(constructor, parameterIndex);
    methodParameter.setContainingClass(implementationClass);
    return forMethodParameter(methodParameter);
}
public static ResolvableType forConstructorParameter(Constructor<?> constructor, int parameterIndex) {
    Assert.notNull(constructor, "Constructor must not be null");
    return forMethodParameter(new MethodParameter(constructor, parameterIndex));
}

可以看到,forConstructor系列方法最后都调用了forMethod系列方法,我们直接分析forMethod系列的方法


forMethod系列方法


微信图片_20221112215649.png

主要分为两类方法

1.forMethodParameter,解决方法参数上的类型问题

2.forMethodReturnType,解决方法返回值的类型问题


forMethodParameter

public class ResolvableTypeDemo {
    public void test(List<String> list, Map<String, List<Integer>> map) {
    }
    public static void main(String[] args) throws Exception {
        Class<ResolvableTypeDemo> resolvableTypeDemoClass = ResolvableTypeDemo.class;
        Method[] declaredMethods = resolvableTypeDemoClass.getDeclaredMethods();
        Method test = declaredMethods[1];
        // 获取方法的第一个参数对应的ResolvableType,参数为-1代表返回值,0为第一个,1为第二个,一次增加
        ResolvableType resolvableType0 = ResolvableType.forMethodParameter(test, 0);
        System.out.println(resolvableType0.resolve());
        System.out.println(resolvableType0.getType());
         // 获取方法的第二个参数对应的ResolvableType
        ResolvableType resolvableType1 = ResolvableType.forMethodParameter(test, 1);
        System.out.println(resolvableType1.resolve());
        System.out.println(resolvableType1.getType());
    }
}

forMethodReturnType

public static ResolvableType forMethodReturnType(Method method) {
    Assert.notNull(method, "Method must not be null");
    return forMethodParameter(new MethodParameter(method, -1));
}

调用逻辑很简单,调用forMethodParameter,并将方法的参数索引替换为-1,代表返回值


forConstructor系列方法


构造函数就是一个特殊的方法,所以都是直接调用的forMethod系列方法,这里就不多介绍了


forField系列方法


专门用于处理字段的类型,如下:

微信图片_20221112215845.png

测试方法Demo

public class ResolvableTypeDemo {
    List<String> stringList;
    List<List<String>> lists;
    public static void main(String[] args) throws Exception {
        Class<ResolvableTypeDemo> resolvableTypeDemoClass = ResolvableTypeDemo.class;
        Field[] declaredFields = resolvableTypeDemoClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println("=======字段名称"+declaredField.getName()+"=========");
            System.out.println("nestingLevel为1");
            ResolvableType resolvableType1 = ResolvableType.forField(declaredField,1);
            System.out.println(resolvableType1.getType());
            System.out.println(resolvableType1.resolve());
            System.out.println("nestingLevel为2");
            ResolvableType resolvableType2 = ResolvableType.forField(declaredField,2);
            System.out.println(resolvableType2.getType());
            System.out.println(resolvableType2.resolve());
            System.out.println("nestingLevel为3");
            ResolvableType resolvableType3 = ResolvableType.forField(declaredField,3);
            System.out.println(resolvableType3.getType());
            System.out.println(resolvableType3.resolve());
        }
    }
}

程序打印:

=======字段名称stringList=========
nestingLevel为1
java.util.List<java.lang.String>
interface java.util.List
nestingLevel为2
class java.lang.String
class java.lang.String
nestingLevel为3
org.springframework.core.ResolvableType$EmptyType@723279cf
null
=======字段名称lists=========
nestingLevel为1
java.util.List<java.util.List<java.lang.String>>
interface java.util.List
nestingLevel为2
java.util.List<java.lang.String>
interface java.util.List
nestingLevel为3
class java.lang.String
class java.lang.String

在上面的所有方法,最后都会调用一个forType方法,所以我们着重也就分析这个系列的方法


forType系列(源码分析)


微信图片_20221112220055.png

最终都会调用到这个方法中,源码如下:

static ResolvableType forType(
    @Nullable Type type, @Nullable TypeProvider typeProvider, @Nullable VariableResolver variableResolver) {
    // 这里可以看出,即使我们提供了一个typeProvider,也不会直接调用它的getType返回,而是会进行一层包装,这个是为什么呢?我们稍后分析
    if (type == null && typeProvider != null) {
        type = SerializableTypeWrapper.forTypeProvider(typeProvider);
    }
    if (type == null) {
        // 自身定义的一个常量,ResolvableType NONE = new ResolvableType(EmptyType.INSTANCE, null, null, 0);
        return NONE;
    }
  // 如果是原始的数据类型(一个简单的Class引用),那么直接封装后返回,这里不做缓存,因为没有上面昂贵的开销
    if (type instanceof Class) {
        return new ResolvableType(type, typeProvider, variableResolver, (ResolvableType) null);
    }
  // 省略缓存相关的代码。。。
    return resultType;
}

上面这段代码比较核心的就是SerializableTypeWrapper.forTypeProvider(typeProvider),我之前也提到了一个问题,为什么要多包装一层呢?这么做的目的主要就是为了得到一个可以进行序列化的Type。

它的核心代码如下:

static Type forTypeProvider(TypeProvider provider) {
    // 直接从provider获取到具体的类型
    Type providedType = provider.getType();
    if (providedType == null || providedType instanceof Serializable) {
        // 如果本身可以序列化的直接返回,例如Java.lang.Class。
        // 如果不能进行序列化,多进行一层包装
        return providedType;
    }
    // 不用管这段代码,我们开发过程中必定不成立
    if (GraalDetector.inImageCode() || !Serializable.class.isAssignableFrom(Class.class)) {
        return providedType;
    }
    // 从缓存中获取
    Type cached = cache.get(providedType);
    if (cached != null) {
        return cached;
    }
    // 遍历支持的集合,就是GenericArrayType.class, ParameterizedType.class, TypeVariable.class, WildcardType.class,处理这个四种类型
    for (Class<?> type : SUPPORTED_SERIALIZABLE_TYPES) {
        if (type.isInstance(providedType)) {
            ClassLoader classLoader = provider.getClass().getClassLoader();
            // 创建的代理类实现的接口,type就不用说了代理类跟目标类必须是同一个类型
            // SerializableTypeProxy:标记接口,标志是一个代理类
            // Serializable:代表可以被序列化
            Class<?>[] interfaces = new Class<?>[] {type, SerializableTypeProxy.class, Serializable.class};
            // 核心代码:TypeProxyInvocationHandler是什么?
            InvocationHandler handler = new TypeProxyInvocationHandler(provider);
            // 依赖于先前的InvocationHandler,以当前的type为目标对象创建了一个代理对象
            // 
            cached = (Type) Proxy.newProxyInstance(classLoader, interfaces, handler);
            cache.put(providedType, cached);
            return cached;
        }
    }
    throw new IllegalArgumentException("Unsupported Type class: " + providedType.getClass().getName());
}

解析来我们分下下TypeProxyInvocationHandler这个类

private static class TypeProxyInvocationHandler implements InvocationHandler, Serializable {
    private final TypeProvider provider;
    public TypeProxyInvocationHandler(TypeProvider provider) {
        this.provider = provider;
    }
    @Override
    @Nullable
    public Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable {
        // 复写目标类的equals方法
        if (method.getName().equals("equals") && args != null) {
            Object other = args[0];
            // Unwrap proxies for speed
            if (other instanceof Type) {
                other = unwrap((Type) other);
            }
            return ObjectUtils.nullSafeEquals(this.provider.getType(), other);
        }
        // 复写目标类的hashCode方法
        else if (method.getName().equals("hashCode")) {
            return ObjectUtils.nullSafeHashCode(this.provider.getType());
        }
         // 复写目标类的getTypeProvider方法
        else if (method.getName().equals("getTypeProvider")) {
            return this.provider;
        }
        // 之所以不直接返回method.invoke(this.provider.getType(), args);也是为了缓存
        // 空参的时候才能缓存,带参数的话不能缓存,因为每次调用传入的参数可能不一样
        if (Type.class == method.getReturnType() && args == null) {
            return forTypeProvider(new MethodInvokeTypeProvider(this.provider, method, -1));
        }
        else if (Type[].class == method.getReturnType() && args == null) {
            Type[] result = new Type[((Type[]) method.invoke(this.provider.getType())).length];
            for (int i = 0; i < result.length; i++) {
                result[i] = forTypeProvider(new MethodInvokeTypeProvider(this.provider, method, i));
            }
            return result;
        }
        try {
            return method.invoke(this.provider.getType(), args);
        }
        catch (InvocationTargetException ex) {
            throw ex.getTargetException();
        }
    }
}

总结


在这篇文章中我们主要学习了java的Type机制,如下:

微信图片_20221112220246.png

Type主要是用来处理泛型的,但是通过Java原始的这一套,处理起来及其的繁琐,所以Spring自行封装了一个ResolvableType,我们在处理类,方法,构造函数,字段时,只需要调用对应的方法就能返回一个对应的ResolvableType,一个ResolvableType就封装了对应的这个对象的原始类型,泛型等等,封装了Java中的所有类型。从这里也能看出Spring的牛逼之处,处理提供了IOC,AOP这两个强大的功能,还封装了一系列的简单易用的工具类。


相关文章
|
6天前
|
Java 应用服务中间件 数据库连接
Java 新手入门:Spring Boot 启动揭秘,小白也能秒懂的超详细指南
Java 新手入门:Spring Boot 启动揭秘,小白也能秒懂的超详细指南
22 2
|
5天前
|
安全 Java 数据库
一天十道Java面试题----第四天(线程池复用的原理------>spring事务的实现方式原理以及隔离级别)
这篇文章是关于Java面试题的笔记,涵盖了线程池复用原理、Spring框架基础、AOP和IOC概念、Bean生命周期和作用域、单例Bean的线程安全性、Spring中使用的设计模式、以及Spring事务的实现方式和隔离级别等知识点。
|
3天前
|
人工智能 自然语言处理 Java
Spring AI,Spring团队开发的新组件,Java工程师快来一起体验吧
文章介绍了Spring AI,这是Spring团队开发的新组件,旨在为Java开发者提供易于集成的人工智能API,包括机器学习、自然语言处理和图像识别等功能,并通过实际代码示例展示了如何快速集成和使用这些AI技术。
Spring AI,Spring团队开发的新组件,Java工程师快来一起体验吧
|
5天前
|
前端开发 Java 数据库连接
一天十道Java面试题----第五天(spring的事务传播机制------>mybatis的优缺点)
这篇文章总结了Java面试中的十个问题,包括Spring事务传播机制、Spring事务失效条件、Bean自动装配方式、Spring、Spring MVC和Spring Boot的区别、Spring MVC的工作流程和主要组件、Spring Boot的自动配置原理和Starter概念、嵌入式服务器的使用原因,以及MyBatis的优缺点。
|
4天前
|
缓存 Java Spring
Java本地高性能缓存实践问题之的Spring Boot中启用缓存支持问题如何解决
Java本地高性能缓存实践问题之的Spring Boot中启用缓存支持问题如何解决
|
5天前
|
Java
【Java】内部类、枚举、泛型
【Java】内部类、枚举、泛型
|
5天前
|
设计模式 前端开发 Java
Spring,作为Java程序员的你能想到什么呢?
该文章主要介绍了Spring框架对于Java程序员的意义,包括Spring框架的一些核心能力和为什么它是如此重要。
|
6天前
|
JavaScript 前端开发 网络协议
WebSocket在Java Spring Boot+Vue框架中实现消息推送功能
在现代Web应用中,实时消息提醒是一项非常重要的功能,能够极大地提升用户体验。WebSocket作为一种在单个TCP连接上进行全双工通信的协议,为实现实时消息提醒提供了高效且低延迟的解决方案。本文将详细介绍如何在Java Spring Boot后端和Vue前端框架中利用WebSocket实现消息提醒功能。
19 0
|
6天前
|
前端开发 Java Spring
Java 新手入门:Spring Boot 轻松整合 Spring 和 Spring MVC!
Java 新手入门:Spring Boot 轻松整合 Spring 和 Spring MVC!
17 0
|
2月前
|
Java API 容器
Java泛型的继承和通配符
Java泛型的继承和通配符
19 1