一个 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。
整个链路是这样的:
具体来说:
- 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
}
}
这段代码在做什么?画个图:
关键点:
getClass()返回的是实际类型UserService.class,不是BaseServiceImpl.classgetGenericSuperclass()返回的是带泛型参数的父类 ,即BaseServiceImpl<UserMapper, User>- 虽然运行时泛型会被擦除,但子类继承父类时指定的泛型参数,会保留在子类的字节码里
- 增强版支持多层继承,即使中间隔了几层,也能正确提取
这就是那个"漏洞"------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> 会报错------Long 是 Serializable 的子类,但 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 层一样,都是利用子类保留父类泛型参数的特性:
- >() {}
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
关键点 :匿名类 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 层用的是同一个原理:
- >() {}"]
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 是怎么工作的。
如果你也有类似的"好像懂了但又说不清楚"的技术点,欢迎在评论区聊聊,说不定可以一起拆解一下。
另外,泛型这块你们在实际项目中遇到过什么坑?或者有什么更骚的用法?也欢迎分享。