你真的懂泛型吗?手写 MyBatis-Plus + Jackson,揭秘框架设计的精髓

一个 Demo 搞定 Java 泛型工程实战------手写 MyBatis-Plus + Jackson 泛型核心,从此不再懵

我们要干什么

先说清楚这篇文章的目的:我们要手写一个 Demo,把 MyBatis-Plus 和 Jackson 中泛型设计的核心逻辑抄过来,简化到极致,让你一次搞透 Java 泛型在工程中的两种主流用法。

为什么选这两个框架?因为它们代表了泛型的两种典型场景:

  • MyBatis-Plus :你写 UserMapper extends BaseMapper<User>,然后就能 userMapper.selectById(1) 直接拿到 User 对象,不用强转。它是怎么知道返回 User 的?
  • Jackson :你写 new TypeReference<List<User>>(){},然后 JSON 就能正确反序列化成 List<User>,而不是 List<LinkedHashMap>。那对大括号是什么鬼?

这两个问题,本质上是同一个问题的两种解法。搞懂了,以后遇到任何框架的泛型设计,你都能看明白。

注意:我们不是在教你怎么用 MyBatis-Plus 和 Jackson,而是手写它们的泛型核心逻辑。 整个 Demo 不依赖这两个框架,纯手写,去掉所有干扰项,只保留泛型相关的代码。

Demo 整体设计

我们模拟一个最常见的业务场景:查询用户列表,分页返回,最后转成 JSON。

整个链路是这样的:

flowchart LR A["Controller"] --> B["Service"] --> C["Dao"] --> D["Mock数据"] A --> E["JSON转换器"] style A fill:#e3f2fd style B fill:#f3e5f5 style C fill:#e8f5e9 style D fill:#fff3e0 style E fill:#ffccbc

具体来说:

  • Controller 层:接收请求,调用 Service,拿到结果后转成 JSON 返回
  • Service 层 :调用 Dao,封装分页逻辑,返回 PageInfo<User>
  • Dao 层 :模拟数据库查询,返回 List<User>
  • JSON 层:手写一个简化版的 JSON 转换器,演示 TypeReference 的原理

整个 Demo 跑起来之后,你访问 /user/page,会看到完整的泛型提取过程打印在控制台上。

先看效果:跑起来是什么样

启动项目,访问 http://localhost:8081/user/page(注意端口是 8081),返回结果:

json 复制代码
{
  "code": 200,
  "message": "success",
  "data": {
    "pageNum": 1,
    "pageSize": 3,
    "total": 10,
    "list": [
      {"id": 1, "name": "用户1", "age": 21},
      {"id": 2, "name": "用户2", "age": 22},
      {"id": 3, "name": "用户3", "age": 23}
    ]
  }
}

这个返回结构是 Result<PageInfo<User>>,三层泛型嵌套。

更有意思的是控制台输出,我故意打印了详细的泛型提取过程。先看 Service 层启动时的输出:

ini 复制代码
============================================================
【泛型提取演示 - 增强版】BaseServiceImpl 构造函数执行中...
当前子类:UserService
============================================================
【多层继承泛型提取】
当前类:UserService
目标父类:BaseServiceImpl
提取索引:1
  → 第1层父类:com.surfing.generics.service.BaseServiceImpl<com.surfing.generics.dao.UserMapper, com.surfing.generics.entity.User>
    ✓ 找到目标父类!
    ✓ 泛型参数列表:[interface com.surfing.generics.dao.UserMapper, class com.surfing.generics.entity.User]
✅ 提取到泛型类型:User
============================================================
✅ 泛型提取成功!entityClass = User
============================================================

再看调用接口时 JSON 层的输出:

sql 复制代码
============================================================
【泛型恢复演示】TypeReference 构造函数执行中...
  步骤1 - 获取泛型父类:
    TypeReference<Result<PageInfo<User>>>
  步骤2 - 提取泛型参数:
    Result<PageInfo<User>>
  
【解析嵌套泛型】
  原始类型(rawType):Result
  泛型参数(typeArgs):[PageInfo<User>]
    → 发现嵌套泛型:PageInfo<User>
      嵌套原始类型:PageInfo
      嵌套泛型参数:[class User]
✅ 泛型恢复成功!
============================================================

看到没?泛型信息在运行时被完整地提取出来了。接下来我们一层层拆解,看看这是怎么做到的。

Service 层:仿 MyBatis-Plus 的泛型绑定

先看怎么用

我们的 UserService 长这样:

java 复制代码
@Service
public class UserService extends BaseServiceImpl<UserMapper, User> {
    // 空的!什么都不用写
}

就这一行继承声明,UserService 就自动获得了所有 CRUD 能力,而且返回的是 User 类型,不是 Object

调用的时候:

java 复制代码
User user = userService.getById(1L);           // 直接返回 User,不用强转
PageInfo<User> page = userService.page(1, 10); // 返回 PageInfo<User>

这就是 MyBatis-Plus 的 ServiceImpl 设计思路。

为什么能这样?不这么做会怎样?

如果没有泛型,你得这么写:

java 复制代码
public class UserService {
    public User getById(Long id) {
        Object result = baseMapper.selectById(id);
        return (User) result;  // 每次都要强转!
    }
}

public class OrderService {
    public Order getById(Long id) {
        Object result = baseMapper.selectById(id);
        return (Order) result;  // 又是强转!
    }
}

每个 Service 都要写一遍一模一样的代码,唯一的区别就是强转的类型不同。这不是复制粘贴吗?

有了泛型,我们把这些重复逻辑抽到 BaseServiceImpl<T> 里,子类只需要声明 T 是什么,父类自动处理剩下的事情。

但问题来了 :父类 BaseServiceImpl 怎么知道 T 具体是什么类型?

这就是关键所在。

核心代码:父类怎么知道子类的泛型参数

增强版本:支持多层继承和接口泛型提取

java 复制代码
public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements IBaseService<T> {
    
    protected Class<T> entityClass;  // 保存实际的实体类型
    
    public BaseServiceImpl() {
        System.out.println("\n" + "=".repeat(60));
        System.out.println("【泛型提取演示 - 增强版】BaseServiceImpl 构造函数执行中...");
        System.out.println("当前子类:" + getClass().getSimpleName());
        
        try {
            // 【增强】:使用 TypeUtils 支持多层继承
            this.entityClass = TypeUtils.extractGenericType(
                getClass(), 
                BaseServiceImpl.class, 
                1  // 第二个参数是 T
            );
            
            System.out.println("✅ 泛型提取成功!entityClass = " + entityClass.getSimpleName());
        } catch (Exception e) {
            System.out.println("⚠️  从父类提取失败,尝试从接口提取...");
            
            // 【增强】:尝试从接口提取(如果有的话)
            try {
                this.entityClass = TypeUtils.extractGenericTypeFromInterface(
                    getClass(),
                    IBaseService.class,
                    0  // IBaseService<T> 的第一个参数
                );
                System.out.println("✅ 从接口提取成功!entityClass = " + entityClass.getSimpleName());
            } catch (Exception e2) {
                System.out.println("❌ 泛型提取失败:" + e2.getMessage());
                throw new IllegalStateException("无法提取实体类型", e2);
            }
        }
        
        System.out.println("=".repeat(60) + "\n");
    }
    
    @Override
    public T getById(Serializable id) {
        return baseMapper.selectById(id);  // 返回类型自动是 T
    }
}

这段代码在做什么?画个图:

sequenceDiagram participant US as UserService participant BSI as BaseServiceImpl participant TU as TypeUtils participant JVM as JVM 类元数据 Note over US: Spring 创建 UserService 实例 US->>BSI: 调用父类构造函数 BSI->>TU: extractGenericType(UserService.class, BaseServiceImpl.class, 1) TU->>JVM: 递归查找带泛型的父类 JVM-->>TU: BaseServiceImpl Note over TU: 关键!这里能拿到完整的泛型信息 TU->>TU: getActualTypeArguments() Note over TU: 返回 [UserMapper.class, User.class] TU-->>BSI: 返回 User.class BSI->>BSI: entityClass = User.class Note over BSI: 泛型类型提取成功!

关键点

  1. getClass() 返回的是实际类型 UserService.class,不是 BaseServiceImpl.class
  2. getGenericSuperclass() 返回的是带泛型参数的父类 ,即 BaseServiceImpl<UserMapper, User>
  3. 虽然运行时泛型会被擦除,但子类继承父类时指定的泛型参数,会保留在子类的字节码里
  4. 增强版支持多层继承,即使中间隔了几层,也能正确提取

这就是那个"漏洞"------Java 类型擦除擦的是泛型变量 T,但不会擦除 class UserService extends BaseServiceImpl<UserMapper, User> 这个继承声明里的具体类型。

为什么必须通过继承?直接传 Class 不行吗?

你可能会想,为什么不直接这样:

java 复制代码
BaseServiceImpl<User> service = new BaseServiceImpl<>(User.class);

当然可以,但每次创建都要传一个 User.class,麻烦。而且如果泛型参数很复杂,比如 Map<String, List<User>>,你传什么?Map.class?那泛型信息还是丢了。

通过继承的方式,类型信息写在类定义里,编译器帮你检查,运行时又能提取出来,两全其美。

Dao 层:泛型接口的设计

Dao 层相对简单,就是定义一个泛型接口:

java 复制代码
public interface BaseMapper<T> {
    T selectById(Serializable id);
    List<T> selectList();
    int insert(T entity);
    List<T> selectBatchIds(Collection<? extends Serializable> idList);
}

然后具体的 Mapper 继承它:

java 复制代码
public interface UserMapper extends BaseMapper<User> {
    // T 绑定为 User
}

这里有个细节值得说说:selectBatchIds 的参数是 Collection<? extends Serializable>,这是通配符的用法。

为什么不直接写 Collection<Serializable>?因为那样的话,你传 List<Long> 会报错------LongSerializable 的子类,但 List<Long> 不是 List<Serializable> 的子类(泛型不是协变的)。

用了 ? extends Serializable,就可以接受任何元素类型是 Serializable 子类的集合了。这是泛型通配符最常见的用法。

JSON 层:仿 Jackson 的 TypeReference

先看问题:为什么反序列化泛型会出错

假设我们有个 JSON:

json 复制代码
[{"id": 1, "name": "张三"}, {"id": 2, "name": "李四"}]

想把它转成 List<User>

java 复制代码
List<User> users = jsonConverter.fromJson(json, List.class);

结果拿到的 users 里面不是 User 对象,而是 LinkedHashMap

为什么?因为 List.class 只告诉转换器"这是个 List",但 List 里面装什么,它不知道。类型擦除把 <User> 这个信息擦掉了。

解决方案:TypeReference

Jackson 的解决方案是让你这样写:

java 复制代码
List<User> users = objectMapper.readValue(json, new TypeReference<List<User>>() {});

注意最后那对大括号 {},它创建了一个匿名内部类

我们手写一个简化版的 TypeReference:

java 复制代码
public abstract class TypeReference<T> {
    
    protected final Type type;
    
    protected TypeReference() {
        // 【核心】从匿名类的父类中提取泛型参数
        Type superClass = getClass().getGenericSuperclass();
        if (superClass instanceof ParameterizedType) {
            this.type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
        } else {
            throw new RuntimeException("必须通过匿名类使用 TypeReference");
        }
    }
    
    public Type getType() {
        return type;
    }
}

原理和 Service 层一样,都是利用子类保留父类泛型参数的特性:

sequenceDiagram participant Code as 业务代码 participant Anon as 匿名类 participant TR as TypeReference participant JVM as JVM 类元数据 Code->>Anon: new TypeReference>() {} Note over Anon: {} 创建了一个匿名内部类 Note over Anon: 这个匿名类 extends TypeReference> Anon->>TR: 调用父类构造函数 TR->>TR: getClass() Note over TR: 返回匿名类的 Class TR->>JVM: getGenericSuperclass() JVM-->>TR: TypeReference> TR->>TR: getActualTypeArguments()[0] Note over TR: 拿到 List TR->>TR: this.type = List

关键点 :匿名类 new TypeReference<List<User>>(){} 本质上是定义了一个新类,这个类继承自 TypeReference<List<User>>,而这个继承关系会被记录在字节码里。

为什么 TypeReference 是抽象类?

注意 TypeReference 声明为 abstract。这是故意的------强制你必须用匿名类的方式创建。

如果它不是抽象类,你可能会这样写:

java 复制代码
TypeReference<List<User>> ref = new TypeReference<List<User>>();  // 没有大括号

这样就不会创建匿名类,getGenericSuperclass() 返回的是 TypeReference<T>T 已被擦除),而不是 TypeReference<List<User>>,泛型信息就丢了。

把它设计成抽象类,编译器就会强制你加上 {},从而创建匿名类。这是一个很巧妙的设计。

两种方式的本质对比

搞到这里,你会发现 Service 层和 JSON 层用的是同一个原理

flowchart TB subgraph 类型擦除的漏洞 L["子类字节码会保留父类泛型参数"] end subgraph 利用方式一 M1["定义类时继承"] M1a["class UserService extends BaseServiceImpl"] M1 --> M1a end subgraph 利用方式二 M2["调用时创建匿名类"] M2a["new TypeReference>() {}"] M2 --> M2a end 类型擦除的漏洞 --> 利用方式一 类型擦除的漏洞 --> 利用方式二 利用方式一 --> R1["类型在编译期确定,适合 DAO/Service"] 利用方式二 --> R2["类型在运行时传递,适合 JSON/序列化"] style 类型擦除的漏洞 fill:#fff9c4 style 利用方式一 fill:#c8e6c9 style 利用方式二 fill:#bbdefb

区别在于使用场景

  • 编译期绑定 (MyBatis-Plus 模式):类型在写代码的时候就确定了。UserMapper 就是操作 User,不会变。好处是 IDE 能做类型检查,写错了直接报红。
  • 运行时传递 (Jackson 模式):类型在调用的时候才确定。同一个方法,这次解析 List<User>,下次解析 Map<String, Order>。更灵活,但类型错误只有运行时才会发现。

泛型进阶:四个高级场景

基础 Demo 跑通了,但工程中还有更复杂的场景。我们参考 MyBatis-Plus 的 ReflectionKit 和 Spring 的 ResolvableType 源码,把四个泛型精华也实现了,并封装在 TypeUtils 工具类中。

TypeUtils 工具类:泛型处理的瑞士军刀

我们创建了一个 TypeUtils 工具类,集中处理所有泛型相关的复杂场景:

java 复制代码
public class TypeUtils {
    // 功能1:从类的继承链中提取泛型参数(支持多层继承)
    public static <T> Class<T> extractGenericType(Class<?> clazz, Class<?> genericSuperclass, int index);
    
    // 功能2:从类实现的接口中提取泛型参数
    public static <T> Class<T> extractGenericTypeFromInterface(Class<?> clazz, Class<?> genericInterface, int index);
    
    // 功能3:解析 Type,处理通配符和边界
    public static Class<?> resolveType(Type type);
    
    // 功能4:解析类型变量,将 T 替换为实际类型
    public static Type resolveTypeVariable(Class<?> targetClass, Type typeToResolve, Class<?> declaringClass);
    
    // 辅助:打印类型的详细信息(调试用)
    public static void printTypeInfo(Type type);
}

这个工具类是整个增强版的核心,下面逐个场景介绍。

场景一:多层继承的泛型提取

实际项目中经常有多层继承:

java 复制代码
public class TestUserService extends AbstractUserService { }
public abstract class AbstractUserService extends BaseServiceImpl<UserMapper, User> { }

这时候 TestUserService.getGenericSuperclass() 返回的是 AbstractUserService(不带泛型参数),得继续往上找。

我们在 TypeUtils 中实现了递归向上查找:

java 复制代码
public static <T> Class<T> extractGenericType(Class<?> clazz, Class<?> genericSuperclass, int index) {
    System.out.println("\n" + "=".repeat(60));
    System.out.println("【多层继承泛型提取】");
    System.out.println("当前类:" + clazz.getSimpleName());
    System.out.println("目标父类:" + genericSuperclass.getSimpleName());
    System.out.println("提取索引:" + index);
    
    Class<?> currentClass = clazz;
    int level = 0;
    
    // 递归向上查找带泛型参数的父类
    while (currentClass != null && currentClass != Object.class) {
        Type superType = currentClass.getGenericSuperclass();
        
        System.out.println("  → 第" + (++level) + "层父类:" + 
            (superType != null ? superType.getTypeName() : "null"));
        
        if (superType instanceof ParameterizedType) {
            ParameterizedType parameterized = (ParameterizedType) superType;
            Type rawType = parameterized.getRawType();
            
            // 检查是否是目标父类
            if (rawType == genericSuperclass) {
                Type[] typeArgs = parameterized.getActualTypeArguments();
                System.out.println("    ✓ 找到目标父类!");
                System.out.println("    ✓ 泛型参数列表:" + java.util.Arrays.toString(typeArgs));
                
                if (index >= 0 && index < typeArgs.length) {
                    Type targetType = typeArgs[index];
                    Class<T> result = (Class<T>) resolveType(targetType);
                    System.out.println("✅ 提取到泛型类型:" + result.getSimpleName());
                    System.out.println("=".repeat(60) + "\n");
                    return result;
                }
            } else {
                System.out.println("    ✗ 不是目标父类,继续向上查找...");
            }
        } else {
            System.out.println("    ✗ 无泛型参数,继续向上查找...");
        }
        
        currentClass = currentClass.getSuperclass();  // 继续往上
    }
    
    throw new IllegalStateException("无法提取泛型参数:" + clazz.getName());
}

测试一下三层继承结构(访问 http://localhost:8081/debug/multi-level),控制台输出:

ini 复制代码
============================================================
【多层继承泛型提取】
当前类:TestUserService
目标父类:BaseServiceImpl
提取索引:1
  → 第1层父类:com.surfing.generics.test.AbstractUserService
    ✗ 无泛型参数,继续向上查找...
  → 第2层父类:com.surfing.generics.service.BaseServiceImpl<com.surfing.generics.dao.UserMapper, com.surfing.generics.entity.User>
    ✓ 找到目标父类!
    ✓ 泛型参数列表:[interface com.surfing.generics.dao.UserMapper, class com.surfing.generics.entity.User]
✅ 提取到泛型类型:User
============================================================

场景二:从接口提取泛型

如果泛型参数在接口上,不在父类上:

java 复制代码
public interface IGenericService<T> {
    T getById(Serializable id);
    List<T> list();
}

public class UserGenericService implements IGenericService<User> { }

这时候要用 getGenericInterfaces() 而不是 getGenericSuperclass()

java 复制代码
public static <T> Class<T> extractGenericTypeFromInterface(Class<?> clazz, Class<?> genericInterface, int index) {
    System.out.println("\n" + "=".repeat(60));
    System.out.println("【接口泛型提取】");
    System.out.println("当前类:" + clazz.getSimpleName());
    System.out.println("目标接口:" + genericInterface.getSimpleName());
    
    Type[] interfaces = clazz.getGenericInterfaces();
    System.out.println("  → 检查 " + interfaces.length + " 个接口");
    
    for (int i = 0; i < interfaces.length; i++) {
        Type iface = interfaces[i];
        System.out.println("    [" + i + "] " + iface.getTypeName());
        
        if (iface instanceof ParameterizedType) {
            ParameterizedType parameterized = (ParameterizedType) iface;
            Type rawType = parameterized.getRawType();
            
            // 检查是否是目标接口
            if (rawType == genericInterface) {
                Type[] typeArgs = parameterized.getActualTypeArguments();
                System.out.println("      ✓ 找到目标接口!");
                System.out.println("      ✓ 泛型参数列表:" + java.util.Arrays.toString(typeArgs));
                
                if (index >= 0 && index < typeArgs.length) {
                    Type targetType = typeArgs[index];
                    Class<T> result = (Class<T>) resolveType(targetType);
                    System.out.println("✅ 提取到泛型类型:" + result.getSimpleName());
                    System.out.println("=".repeat(60) + "\n");
                    return result;
                }
            }
        }
    }
    
    throw new IllegalStateException("无法从接口提取泛型参数:" + clazz.getName());
}

测试访问 http://localhost:8081/debug/interface,控制台输出:

ini 复制代码
============================================================
【接口泛型提取】
当前类:UserGenericService
目标接口:IGenericService
  → 检查 1 个接口
    [0] com.surfing.generics.test.IGenericService<com.surfing.generics.entity.User>
      ✓ 找到目标接口!
      ✓ 泛型参数列表:[class com.surfing.generics.entity.User]
✅ 提取到泛型类型:User
============================================================

场景三:通配符和边界的处理

当泛型参数带通配符时,比如 List<? extends User>getActualTypeArguments() 返回的不是 Class,而是 WildcardType

Java 反射的 Type 体系:

scss 复制代码
Type (接口)
├── Class<?>              // 普通类型:String.class, User.class
├── ParameterizedType     // 参数化类型:List<String>, Map<K,V>
│   ├── getRawType()      // List<String> -> List.class
│   └── getActualTypeArguments()  // List<String> -> [String.class]
├── WildcardType          // 通配符类型:? extends Number, ? super Integer
│   ├── getUpperBounds()  // ? extends User -> [User.class]
│   └── getLowerBounds()  // ? super Integer -> [Integer.class]
├── TypeVariable<?>       // 类型变量:T, E, K, V
│   └── getBounds()       // T extends Number -> [Number.class]
└── GenericArrayType      // 泛型数组:T[], List<String>[]
    └── getGenericComponentType()  // T[] -> T

TypeUtils.resolveType() 方法处理所有这些类型:

java 复制代码
public static Class<?> resolveType(Type type) {
    if (type instanceof Class<?>) {
        return (Class<?>) type;  // 普通类型
    }
    
    if (type instanceof ParameterizedType) {
        return (Class<?>) ((ParameterizedType) type).getRawType();  // List<String> -> List
    }
    
    if (type instanceof WildcardType) {
        WildcardType wildcard = (WildcardType) type;
        // ? extends User → 取上界
        Type[] upperBounds = wildcard.getUpperBounds();
        if (upperBounds.length > 0) {
            return resolveType(upperBounds[0]);
        }
        // ? super Integer → 取下界
        Type[] lowerBounds = wildcard.getLowerBounds();
        if (lowerBounds.length > 0) {
            return resolveType(lowerBounds[0]);
        }
        return Object.class;
    }
    
    if (type instanceof TypeVariable<?>) {
        TypeVariable<?> tv = (TypeVariable<?>) type;
        Type[] bounds = tv.getBounds();  // T extends Number -> Number
        if (bounds.length > 0) {
            return resolveType(bounds[0]);
        }
        return Object.class;
    }
    
    if (type instanceof GenericArrayType) {
        return Object[].class;  // T[] -> Object[]
    }
    
    return Object.class;
}

测试 List<? extends User>(访问 http://localhost:8081/debug/wildcard),控制台输出:

java 复制代码
>>> 测试 1:List<? extends User>
============================================================
【泛型恢复演示】TypeReference 构造函数执行中...
  步骤1 - 获取泛型父类:com.surfing.generics.json.TypeReference<java.util.List<? extends com.surfing.generics.entity.User>>
  步骤2 - 提取泛型参数数组:
    typeArgs[0] = java.util.List<? extends com.surfing.generics.entity.User>
  步骤3 - 提取到类型:java.util.List<? extends com.surfing.generics.entity.User>
✅ 泛型恢复成功!type 已设置
============================================================

【类型信息】
类型名称:java.util.List<? extends com.surfing.generics.entity.User>
类型类别:ParameterizedTypeImpl
  → 原始类型:interface java.util.List
  → 泛型参数:
    [0] ? extends com.surfing.generics.entity.User (WildcardTypeImpl)
        上界:[class com.surfing.generics.entity.User]
============================================================

场景四:类型变量的递归解析

这个是最复杂的场景。考虑这个类:

java 复制代码
public class GenericDao<T> {
    public List<T> findAll() { return null; }
    public Map<String, T> findMap() { return null; }
}

public class UserDao extends GenericDao<User> { }

你想知道 UserDao.findAll() 的返回类型,反射拿到的是 List<T>。但 T 是个类型变量,得结合 UserDao 的继承声明才能知道 T = User

解决方案是构建一个类型映射表,然后递归替换:

java 复制代码
public static Type resolveTypeVariable(Class<?> targetClass, Type typeToResolve, Class<?> declaringClass) {
    System.out.println("\n" + "=".repeat(60));
    System.out.println("【类型变量递归解析】");
    System.out.println("目标类:" + targetClass.getSimpleName());
    System.out.println("要解析的类型:" + typeToResolve.getTypeName());
    System.out.println("声明类:" + declaringClass.getSimpleName());
    
    // 步骤1:构建类型变量映射表 {T -> User}
    Map<TypeVariable<?>, Type> typeVarMap = buildTypeVariableMap(targetClass, declaringClass);
    
    System.out.println("  → 构建类型映射:" + typeVarMap);
    
    // 步骤2:递归替换类型变量
    Type result = substituteTypeVariables(typeToResolve, typeVarMap);
    
    System.out.println("  → 替换后结果:" + result.getTypeName());
    System.out.println("✅ 解析完成");
    System.out.println("=".repeat(60) + "\n");
    
    return result;
}

// 构建类型变量映射表
private static Map<TypeVariable<?>, Type> buildTypeVariableMap(Class<?> targetClass, Class<?> declaringClass) {
    Map<TypeVariable<?>, Type> map = new HashMap<>();
    
    Class<?> current = targetClass;  // UserDao
    while (current != null && !current.equals(declaringClass)) {
        Type superType = current.getGenericSuperclass();
        // → GenericDao<User>
        
        if (superType instanceof ParameterizedType) {
            ParameterizedType pType = (ParameterizedType) superType;
            Class<?> rawType = (Class<?>) pType.getRawType();  // GenericDao
            
            TypeVariable<?>[] typeParams = rawType.getTypeParameters();  // [T]
            Type[] actualArgs = pType.getActualTypeArguments();  // [User]
            
            for (int i = 0; i < typeParams.length; i++) {
                map.put(typeParams[i], actualArgs[i]);  // T -> User
            }
        }
        
        current = current.getSuperclass();
    }
    
    return map;  // {T -> User}
}

// 递归替换类型变量
private static Type substituteTypeVariables(Type type, Map<TypeVariable<?>, Type> map) {
    if (type instanceof TypeVariable<?>) {
        // T -> User
        Type resolved = map.get(type);
        return resolved != null ? resolved : type;
    }
    
    if (type instanceof ParameterizedType) {
        // List<T> -> List<User>
        ParameterizedType pType = (ParameterizedType) type;
        Type[] args = pType.getActualTypeArguments();  // [T]
        Type[] resolvedArgs = new Type[args.length];
        
        for (int i = 0; i < args.length; i++) {
            resolvedArgs[i] = substituteTypeVariables(args[i], map);  // T -> User
        }
        
        // 返回新的 ParameterizedType
        return new ParameterizedTypeImpl(
            pType.getRawType(),  // List
            resolvedArgs,  // [User]
            pType.getOwnerType()
        );
    }
    
    return type;
}

测试访问 http://localhost:8081/debug/type-variable,控制台输出:

markdown 复制代码
>>> 测试 1:UserDao.findAll() 的返回类型
原始返回类型:java.util.List<T>
============================================================
【类型变量递归解析】
目标类:UserDao
要解析的类型:java.util.List<T>
声明类:GenericDao
  → 构建类型映射:{T=class com.surfing.generics.entity.User}
  → 替换后结果:List<User>
✅ 解析完成
============================================================

>>> 测试 2:UserDao.findMap() 的返回类型
原始返回类型:java.util.Map<java.lang.String, T>
============================================================
【类型变量递归解析】
目标类:UserDao
要解析的类型:java.util.Map<java.lang.String, T>
声明类:GenericDao
  → 构建类型映射:{T=class com.surfing.generics.entity.User}
  → 替换后结果:Map<String, User>
✅ 解析完成
============================================================

小结

这四个场景覆盖了 Java 泛型在工程中的绝大部分复杂情况。MyBatis-Plus 的 ReflectionKit、Spring 的 ResolvableType 底层都是这套逻辑。

我们封装的 TypeUtils 工具类提供了多层继承泛型提取、接口泛型提取、通配符边界处理、类型变量递归解析这几个核心能力,基本上能应对工程中遇到的所有泛型场景。

搞懂了这些,以后看任何框架的泛型源码都不慌了。

技术升华:从泛型看框架设计

最后聊点形而上的东西。

Java 泛型的类型擦除经常被吐槽是"历史包袱",但你看 MyBatis-Plus 和 Jackson 的设计,它们不仅绕过了这个限制,还把它变成了优雅的 API 设计。

这背后有个更通用的设计思想:约束即自由。

类型擦除是个约束,但正是这个约束,催生了"继承时绑定泛型"这个模式。如果 Java 是真泛型(像 C#),可能就不会有 BaseMapper<T> 这种设计------直接在运行时拿泛型参数就行了,何必搞继承。

但继承这种方式,反而带来了额外的好处:

  • 类型检查在编译期完成,更安全
  • 代码结构更清晰,UserMapper extends BaseMapper<User> 一眼就知道这是干嘛的
  • 符合面向对象的设计原则

另一个启发是:理解原理比记住 API 更重要。

getGenericSuperclass()getActualTypeArguments() 这两个方法,在很多框架里都会用到。Spring 的依赖注入、Hibernate 的实体映射、各种 RPC 框架的序列化......底层都是这套东西。

搞懂了原理,看源码就不慌了。遇到泛型相关的 Bug,也能快速定位。


最后

这篇文章的 Demo 代码已经跑通了,包括基础版和增强版的完整实现。需要源码的可以在评论区留言,我私发给你。

写这篇的时候,我自己也把一些之前模糊的点搞清楚了。特别是类型变量的递归解析那块,真正理解了 MyBatis-Plus 的 ReflectionKit 是怎么工作的。

如果你也有类似的"好像懂了但又说不清楚"的技术点,欢迎在评论区聊聊,说不定可以一起拆解一下。

另外,泛型这块你们在实际项目中遇到过什么坑?或者有什么更骚的用法?也欢迎分享。

相关推荐
随风飘的云31 分钟前
mysql的in查询列数据量非常大导致数据索引失效的解决方案
后端
凯哥197032 分钟前
离线使用 Docker 镜像
后端
Stream32 分钟前
大模型应用技术之Rerank重排序
后端
古城小栈34 分钟前
SpringBoot:声明式事务 和 编程式事务 的擂台霸业
java·spring boot·后端
用户693717500138438 分钟前
23.Kotlin 继承:继承的细节:覆盖方法与属性
android·后端·kotlin
未来之窗软件服务1 小时前
操作系统应用(三十六)golang语言ER实体图开发—东方仙盟筑基期
后端·golang·mermaid·仙盟创梦ide·东方仙盟·操作系统应用
user_永1 小时前
Maven 发包
后端
幌才_loong1 小时前
.NET8 牵手 Log4Net:日志界 “最佳 CP” 出道,调试再也不秃头!
后端
武子康1 小时前
大数据-173 Elasticsearch 映射与文档增删改查实战(基于 7.x/8.x)JSON
大数据·后端·elasticsearch