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这两个强大的功能,还封装了一系列的简单易用的工具类。


相关文章
|
3月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
121 2
|
27天前
|
Java Spring
Java Spring Boot监听事件和处理事件
通过上述步骤,我们可以在Java Spring Boot应用中实现事件的发布和监听。事件驱动模型可以帮助我们实现组件间的松耦合,提升系统的可维护性和可扩展性。无论是处理业务逻辑还是系统事件,Spring Boot的事件机制都提供了强大的支持和灵活性。希望本文能为您的开发工作提供实用的指导和帮助。
81 15
|
24天前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
|
2月前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
89 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
3月前
|
人工智能 前端开发 Java
基于开源框架Spring AI Alibaba快速构建Java应用
本文旨在帮助开发者快速掌握并应用 Spring AI Alibaba,提升基于 Java 的大模型应用开发效率和安全性。
291 12
基于开源框架Spring AI Alibaba快速构建Java应用
|
3月前
|
Java
Unable to obtain OffsetDateTime from TemporalAccessor: {},ISO resolved to 2024-11-26T20:55:26 of type java.time.format.Parsed
Unable to obtain OffsetDateTime from TemporalAccessor: {},ISO resolved to 2024-11-26T20:55:26 of type java.time.format.Parsed
88 0
|
4月前
|
Java API
[Java]泛型
本文详细介绍了Java泛型的相关概念和使用方法,包括类型判断、继承泛型类或实现泛型接口、泛型通配符、泛型方法、泛型上下边界、静态方法中使用泛型等内容。作者通过多个示例和测试代码,深入浅出地解释了泛型的原理和应用场景,帮助读者更好地理解和掌握Java泛型的使用技巧。文章还探讨了一些常见的疑惑和误区,如泛型擦除和基本数据类型数组的使用限制。最后,作者强调了泛型在实际开发中的重要性和应用价值。
96 0
[Java]泛型
|
3月前
|
Java 数据库连接 API
Spring 框架的介绍(Java EE 学习笔记02)
Spring是一个由Rod Johnson开发的轻量级Java SE/EE一站式开源框架,旨在解决Java EE应用中的多种问题。它采用非侵入式设计,通过IoC和AOP技术简化了Java应用的开发流程,降低了组件间的耦合度,支持事务管理和多种框架的无缝集成,极大提升了开发效率和代码质量。Spring 5引入了响应式编程等新特性,进一步增强了框架的功能性和灵活性。
69 0
|
3月前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
58 0
|
前端开发 Java 数据库
Java面试题 - Spring
Java面试题 - Spring
150 0