Java泛型类型参数解析方法详解

小豆丁 3小时前 ⋅ 1 阅读
聊天记录
头像

/** * 获取泛型工具助手 */ public static Class<?>[] resolveTypeArguments(final Class<?> clazz, final Class<?> genericIfc) { if (null == GENERIC_TYPE_RESOLVER) { // 直接使用 spring 静态方法,减少对象创建 return SpringReflectionHelper.resolveTypeArguments(clazz, genericIfc); } return GENERIC_TYPE_RESOLVER.resolveTypeArguments(clazz, genericIfc); } 这里clazz必须继承genericIfc这泛型类吗?

头像

是的,clazz必须继承(或实现)genericIfc这个泛型类/接口,否则这个方法将无法正确解析泛型参数。

方法的作用

这个resolveTypeArguments方法的作用是:获取clazz类在实现/继承genericIfc这个泛型类/接口时,为其类型参数提供的具体类型

实际使用场景示例

示例1:继承泛型父类

JAVA
// 泛型父类
public abstract class BaseDao<T, ID> {
    // 处理T类型的实体
}

// 具体实现
public class UserDao extends BaseDao<User, Long> {
    // UserDao继承BaseDao时,指定了T=User, ID=Long
}

// 调用解析
Class<?>[] typeArgs = resolveTypeArguments(UserDao.class, BaseDao.class);
// 返回结果:[User.class, Long.class]

示例2:实现泛型接口

JAVA
// 泛型接口
public interface Repository<T, ID> {
    T findById(ID id);
}

// 具体实现
public class UserRepository implements Repository<User, Long> {
    public User findById(Long id) { /* ... */ }
}

// 调用解析
Class<?>[] typeArgs = resolveTypeArguments(UserRepository.class, Repository.class);
// 返回结果:[User.class, Long.class]

不满足条件的情况

情况1:clazz不继承genericIfc

JAVA
// 没有任何关系的两个类
public class A { }
public class B { }

// 错误调用
Class<?>[] typeArgs = resolveTypeArguments(A.class, B.class);
// 可能返回null或空数组,因为没有继承关系

情况2:genericIfc不是泛型

JAVA
// 非泛型接口
public interface NotGeneric { }

// 实现类
public class MyImpl implements NotGeneric { }

// 调用解析
Class<?>[] typeArgs = resolveTypeArguments(MyImpl.class, NotGeneric.class);
// 可能返回空数组,因为NotGeneric没有泛型参数

方法实现细节

让我们看下这个方法可能的实现逻辑:

JAVA
public static Class<?>[] resolveTypeArguments(final Class<?> clazz, final Class<?> genericIfc) {
    // 1. 验证clazz是否继承/实现了genericIfc
    if (!genericIfc.isAssignableFrom(clazz)) {
        throw new IllegalArgumentException(
            String.format("%s is not assignable from %s", 
                genericIfc.getName(), clazz.getName()));
    }
    
    // 2. 通过反射获取泛型信息
    Type genericSuperclass = clazz.getGenericSuperclass();
    
    // 如果是ParameterizedType(参数化类型)
    if (genericSuperclass instanceof ParameterizedType) {
        ParameterizedType paramType = (ParameterizedType) genericSuperclass;
        // 检查是否是我们要找的接口/父类
        if (paramType.getRawType().equals(genericIfc)) {
            Type[] actualTypeArguments = paramType.getActualTypeArguments();
            // 将Type转换为Class
            Class<?>[] result = new Class[actualTypeArguments.length];
            for (int i = 0; i < actualTypeArguments.length; i++) {
                if (actualTypeArguments[i] instanceof Class) {
                    result[i] = (Class<?>) actualTypeArguments[i];
                }
            }
            return result;
        }
    }
    
    // 3. 递归检查接口
    Type[] genericInterfaces = clazz.getGenericInterfaces();
    for (Type genericInterface : genericInterfaces) {
        if (genericInterface instanceof ParameterizedType) {
            ParameterizedType paramType = (ParameterizedType) genericInterface;
            if (paramType.getRawType().equals(genericIfc)) {
                // 处理类似上面的逻辑
            }
        }
    }
    
    return null; // 或空数组
}

实际应用场景

1. Spring框架中的依赖注入

JAVA
// 在Spring中自动注入特定类型的Repository
@Autowired
private UserRepository userRepository;

// Spring内部需要知道UserRepository的泛型参数,才能进行CRUD操作

2. 通用DAO/Service实现

JAVA
public abstract class BaseServiceImpl<T, ID> implements BaseService<T, ID> {
    protected Class<T> entityClass;
    
    public BaseServiceImpl() {
        // 在构造器中通过工具方法获取T的实际类型
        Class<?>[] typeArgs = resolveTypeArguments(
            getClass(), BaseServiceImpl.class);
        this.entityClass = (Class<T>) typeArgs[0];
    }
}

3. MyBatis/MyBatis-Plus的通用Mapper

JAVA
// 类似MyBatis-Plus的BaseMapper
public interface BaseMapper<T> {
    T selectById(Serializable id);
}

// 具体Mapper
public interface UserMapper extends BaseMapper<User> {
    // 其他方法
}

// MyBatis-Plus需要知道BaseMapper<T>中T的具体类型

注意事项

  1. 类型擦除:由于Java的类型擦除,泛型信息在运行时不可用,但通过这种方式可以在编译时保留
  2. 多层继承:如果有多层继承,可能需要递归查找
  3. 原始类型:如果使用原始类型(没有指定泛型参数),则无法解析
  4. 通配符:如果使用? extends Number这样的通配符,解析会比较复杂

总结

  • clazz必须继承或实现genericIfc,这是方法工作的前提
  • 方法返回的是genericIfc的泛型参数在clazz中对应的具体类型
  • 这是一个在框架开发中常用的技术,用于在运行时获取泛型的具体类型信息
  • Spring框架的ResolvableType就是做类似的事情,提供了更强大的泛型类型解析能力
头像

public abstract class CrudRepository<M extends BaseMapper<T>, T> extends AbstractRepository<M, T> {

@Autowired
protected M baseMapper; 没毛病吧?
头像

这段代码在语法上没有错误,但在设计上存在潜在问题。让我分析一下:

代码分析

JAVA
public abstract class CrudRepository<M extends BaseMapper<T>, T> 
    extends AbstractRepository<M, T> {

    @Autowired
    protected M baseMapper;  // 这里可能存在循环依赖
}

1. 语法正确性

  • ✅ 语法完全正确
  • ✅ 符合Spring自动注入规则
  • ✅ 泛型定义合理

2. 潜在问题

问题1:循环依赖

JAVA
// 假设有一个具体的实现
@Repository
public class UserRepository extends CrudRepository<UserMapper, User> {
    // 父类CrudRepository已经注入了UserMapper
    // 但UserRepository本身也是一个Repository
    // 这可能导致依赖关系复杂化
}

问题2:泛型参数传递过于复杂

JAVA
// 使用时的声明
public class UserRepository extends CrudRepository<UserMapper, User> {
    // 必须指定两个泛型参数
    // 但UserMapper本身已经携带了User的泛型信息
}

// 更好的设计可能是
public class UserRepository extends CrudRepository<User> {
    // 只需要一个泛型参数
}

3. 建议的改进方案

方案1:简化泛型定义

JAVA
public abstract class CrudRepository<T> extends AbstractRepository<BaseMapper<T>, T> {
    
    @Autowired
    protected BaseMapper<T> baseMapper;
    
    // 这样使用更简洁
    // public class UserRepository extends CrudRepository<User>
}

方案2:通过构造函数注入(推荐)

JAVA
public abstract class CrudRepository<M extends BaseMapper<T>, T> 
    extends AbstractRepository<M, T> {

    protected final M baseMapper;
    
    // 通过构造函数注入,更安全
    protected CrudRepository(M baseMapper) {
        this.baseMapper = baseMapper;
    }
}

方案3:使用Setter注入

JAVA
public abstract class CrudRepository<M extends BaseMapper<T>, T> 
    extends AbstractRepository<M, T> {

    protected M baseMapper;
    
    @Autowired
    public void setBaseMapper(M baseMapper) {
        this.baseMapper = baseMapper;
    }
}

4. 完整示例

当前结构

JAVA
// BaseMapper接口
public interface BaseMapper<T> {
    T selectById(Long id);
    int insert(T entity);
    int updateById(T entity);
    int deleteById(Long id);
}

// UserMapper实现
@Mapper
public interface UserMapper extends BaseMapper<User> {
    // 额外的方法
    List<User> selectByAge(Integer age);
}

// UserRepository
@Repository
public class UserRepository extends CrudRepository<UserMapper, User> {
    // 父类已注入baseMapper
    public User findUserWithDetail(Long id) {
        User user = baseMapper.selectById(id);
        // 其他业务逻辑
        return user;
    }
}

改进后的结构

JAVA
// 方案1:简化泛型
public abstract class CrudRepository<T> extends AbstractRepository<BaseMapper<T>, T> {
    
    @Autowired
    protected BaseMapper<T> baseMapper;
    
    // 通用的CRUD方法
    public T findById(Long id) {
        return baseMapper.selectById(id);
    }
}

// 使用
@Repository
public class UserRepository extends CrudRepository<User> {
    // 如果需要UserMapper的特有方法,可以额外注入
    @Autowired
    private UserMapper userMapper;
    
    public List<User> findByAge(Integer age) {
        return userMapper.selectByAge(age);
    }
}

5. 最佳实践建议

方案A:如果不需要Mapper的特有方法

JAVA
public abstract class SimpleCrudRepository<T> {
    
    @Autowired
    private BaseMapper<T> baseMapper;
    
    // 只使用BaseMapper的通用方法
    public T findById(Long id) {
        return baseMapper.selectById(id);
    }
}

方案B:如果需要Mapper的特有方法

JAVA
public abstract class GenericCrudRepository<M extends BaseMapper<T>, T> {
    
    private M mapper;
    
    // 由子类提供具体的Mapper
    protected GenericCrudRepository(M mapper) {
        this.mapper = mapper;
    }
    
    // 可以同时使用通用和特有方法
    protected M getMapper() {
        return this.mapper;
    }
}

// 具体Repository
@Repository
public class UserRepository extends GenericCrudRepository<UserMapper, User> {
    
    public UserRepository(UserMapper userMapper) {
        super(userMapper);
    }
    
    public List<User> findByAge(Integer age) {
        return getMapper().selectByAge(age);
    }
}

6. MyBatis-Plus的最佳实践

如果使用MyBatis-Plus,可以参考其官方做法:

JAVA
// MyBatis-Plus风格的Service
public interface IService<T> {
    // 通用服务接口
}

// 实现
public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {
    
    @Autowired
    protected M baseMapper;
    
    // 实现...
}

总结

你的代码没有语法错误,但有优化空间:

  1. 可行但不够优雅CrudRepository&lt;UserMapper, User&gt; 中的泛型信息有冗余
  2. 依赖注入方式:字段注入虽然简单,但不如构造器注入安全
  3. 设计建议
    • 如果只需要BaseMapper的通用方法,用 CrudRepository&lt;User&gt;
    • 如果需要特定Mapper的特有方法,用构造器注入
    • 考虑使用MyBatis-Plus现有的Service模式

最终建议:如果你只是做简单的CRUD,当前代码可以使用。但如果项目复杂,建议优化设计。

头像

泛型可以spring 可以注入吗?

头像

是的,Spring 可以注入泛型类型的Bean,而且这是Spring框架一个非常重要的特性!

Spring 泛型依赖注入的工作原理

Spring 4.0+ 支持泛型类型作为依赖注入的限定符,这意味着:

1. 基本泛型注入

JAVA
// Repository定义
public class UserRepository implements Repository<User> {
    // ...
}

public class ProductRepository implements Repository<Product> {
    // ...
}

// Service中使用
@Service
public class UserService {
    // Spring会根据泛型类型自动注入UserRepository
    @Autowired
    private Repository<User> repository;  // ✅ 会注入UserRepository
}

2. 多层次泛型也能识别

JAVA
// 多层泛型接口
public interface Repository<T> { }

public interface CrudRepository<T> extends Repository<T> { }

public class UserRepository implements CrudRepository<User> { }

// 可以这样注入
@Service
public class UserService {
    @Autowired
    private Repository<User> repository;      // ✅ 注入UserRepository
    @Autowired
    private CrudRepository<User> repository;  // ✅ 也注入UserRepository
}

你的代码场景分析

在你的例子中:

JAVA
public abstract class CrudRepository<M extends BaseMapper<T>, T> 
    extends AbstractRepository<M, T> {

    @Autowired
    protected M baseMapper;  // Spring 能正确注入
}

为什么能注入?

  1. 类型匹配:Spring 在创建具体的 UserRepository 时,知道 M = UserMapper
  2. 泛型推断:Spring 能通过类型推导确定具体的 Bean
  3. 依赖查找:Spring 会查找类型为 UserMapper 的 Bean 来注入

实际运行示例

完整的可运行例子

JAVA
// 1. 泛型接口
public interface BaseMapper<T> {
    T findById(Long id);
}

// 2. 具体Mapper
@Repository
public class UserMapper implements BaseMapper<User> {
    public User findById(Long id) {
        return new User(id, "test");
    }
}

@Repository  
public class ProductMapper implements BaseMapper<Product> {
    public Product findById(Long id) {
        return new Product(id, "product");
    }
}

// 3. 泛型Repository
public abstract class CrudRepository<M extends BaseMapper<T>, T> {
    
    @Autowired
    protected M baseMapper;  // Spring 会根据子类泛型注入
    
    public T getById(Long id) {
        return baseMapper.findById(id);
    }
}

// 4. 具体Repository
@Repository
public class UserRepository extends CrudRepository<UserMapper, User> {
    // 这里 baseMapper 会被自动注入 UserMapper
}

@Repository
public class ProductRepository extends CrudRepository<ProductMapper, Product> {
    // 这里 baseMapper 会被自动注入 ProductMapper
}

// 5. 使用示例
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    public void test() {
        User user = userRepository.getById(1L);
        System.out.println(user);
    }
}

验证Spring能正确注入的方法

方法1:在构造器中打印验证

JAVA
@Repository
public class UserRepository extends CrudRepository<UserMapper, User> {
    
    public UserRepository() {
        // 测试注入
        System.out.println("baseMapper类型: " + baseMapper.getClass());
        // 输出: baseMapper类型: class com.example.UserMapper
    }
}

方法2:编写测试用例

JAVA
@SpringBootTest
class RepositoryTest {
    
    @Autowired
    private UserRepository userRepository;
    
    @Test
    void testGenericInjection() {
        assertNotNull(userRepository);
        // 通过反射获取baseMapper字段
        Field field = CrudRepository.class.getDeclaredField("baseMapper");
        field.setAccessible(true);
        Object mapper = field.get(userRepository);
        
        assertTrue(mapper instanceof UserMapper);
        assertFalse(mapper instanceof ProductMapper);
    }
}

Spring 泛型注入的限制

虽然Spring支持泛型注入,但有一些限制:

1. 需要具体类型信息

JAVA
// ✅ 可以 - 具体类型
@Autowired
private Repository<User> userRepository;

// ❌ 不可以 - 类型擦除后就是 Repository
@Autowired
private Repository repository;  // 不知道注入哪个实现

2. 运行时类型擦除问题

JAVA
// Java 泛型类型擦除
List<String> stringList = new ArrayList<>();
List<Integer> integerList = new ArrayList<>();

// 运行时类型都是 List
System.out.println(stringList.getClass());  // class java.util.ArrayList
System.out.println(integerList.getClass()); // class java.util.ArrayList

// 但Spring通过ResolvableType保留了泛型信息

3. @Qualifier 结合使用

JAVA
// 如果有多个相同泛型的Bean
@Repository("userRepo")
public class UserRepository implements Repository<User> { }

@Repository("productRepo")  
public class ProductRepository implements Repository<Product> { }

// 使用时需要指定
@Autowired
@Qualifier("userRepo")
private Repository<User> repository;

Spring 的底层实现

Spring 通过 ResolvableType 类来处理泛型:

JAVA
// 获取泛型类型信息
ResolvableType repoType = ResolvableType.forClassWithGenerics(
    CrudRepository.class, 
    UserMapper.class, 
    User.class
);

// Spring在注入时使用
BeanFactory beanFactory = ...;
// 查找匹配的Bean
String[] beanNames = beanFactory.getBeanNamesForType(repoType);

最佳实践建议

1. 推荐使用构造器注入

JAVA
public abstract class CrudRepository<M extends BaseMapper<T>, T> {
    
    protected final M baseMapper;
    
    protected CrudRepository(M baseMapper) {
        this.baseMapper = baseMapper;
    }
}

2. 避免过于复杂的泛型嵌套

JAVA
// ❌ 过于复杂
public class Service<A extends Repository<B>, B extends Entity<C>, C> {
    // 难以维护
}

// ✅ 保持简单
public class UserService {
    private final UserRepository repository;
}

3. 考虑使用具体类

JAVA
// 如果泛型主要是为了类型安全
public abstract class BaseCrudRepository<T> {
    
    private final BaseMapper<T> mapper;
    
    protected BaseCrudRepository(BaseMapper<T> mapper) {
        this.mapper = mapper;
    }
}

// 具体实现
@Repository
public class UserRepository extends BaseCrudRepository<User> {
    public UserRepository(BaseMapper<User> mapper) {
        super(mapper);
    }
}

结论

你的代码完全可以正常工作:

JAVA
@Autowired
protected M baseMapper;  // ✅ Spring 会根据子类的泛型参数正确注入

这是因为:

  1. Spring 4.0+ 完整支持泛型依赖注入
  2. Spring 通过 ResolvableType 保留了泛型信息
  3. 在创建子类实例时,Spring 知道具体的泛型类型
  4. Spring 会根据泛型类型查找匹配的 Bean 进行注入

所以放心使用,这是Spring的标准功能!

全部评论: 0

    我有话说: