Spring泛型字段注入原理与实现

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

我说是泛型类型注入到当前类 @autowired T mapper

头像

Spring 泛型字段注入原理

您问的是当在类中直接注入泛型字段的情况,比如 @Autowired T mapper。这涉及到 Spring 的泛型依赖注入机制,让我详细解释。

1. 基本情况

JAVA
@Component
public class MyService<T> {
    @Autowired
    private T mapper;  // ❌ 这不会正常工作
}

直接注入泛型类型参数是不行的,因为:

  1. 泛型在运行时被擦除,Spring 无法确定具体的类型
  2. 泛型类型参数 T 在运行时是 Object 类型

2. Spring 处理泛型依赖的原理

Spring 注入依赖时,需要知道具体的类型。让我们看几种情况:

2.1 情况一:在泛型基类中注入泛型字段

JAVA
// 基类
public abstract class BaseService<M extends BaseMapper<T>, T> {
    @Autowired
    protected M baseMapper;  // ✅ 这可以工作,但需要特殊处理
}

这看起来像是 Spring 的神奇之处,但实际上需要特定的条件才能工作

3. 如何实现泛型字段注入

3.1 方式一:通过子类确定具体类型

JAVA
// 1. 泛型基类
public abstract class BaseService<M, T> {
    @Autowired
    protected M mapper;  // Spring 在子类具体化时才能确定类型
    
    public abstract T getById(Long id);
}

// 2. 具体子类
@Service
public class UserService extends BaseService<UserMapper, User> {
    // UserService 被实例化时,Spring 知道:
    // M = UserMapper, T = User
    // 然后会注入 UserMapper
    @Override
    public User getById(Long id) {
        return mapper.selectById(id);
    }
}

关键点:当 UserService 被 Spring 管理时,Spring 会:

  1. 解析 UserService 的泛型信息
  2. 知道 BaseService&lt;UserMapper, User&gt; 中的 MUserMapper
  3. 查找类型为 UserMapper 的 Bean 注入

3.2 方式二:通过构造函数注入

JAVA
// 泛型基类
public abstract class BaseService<M, T> {
    protected final M mapper;
    
    public BaseService(M mapper) {
        this.mapper = mapper;
    }
}

// 具体子类
@Service
public class UserService extends BaseService<UserMapper, User> {
    
    @Autowired
    public UserService(UserMapper mapper) {
        super(mapper);  // 将具体类型传递给父类
    }
}

4. MyBatis-Plus 的实现方式

MyBatis-Plus 的 ServiceImpl 实际上没有使用 @Autowired 注入 mapper,它使用了不同的机制。

4.1 MyBatis-Plus 的实际实现

查看 MyBatis-Plus 源码:

JAVA
public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {
    
    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    @Autowired
    protected M baseMapper;  // 这里确实有 @Autowired
    
    // 其他代码...
}

4.2 为什么这能工作?

当有具体子类时:

JAVA
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> {
    // UserServiceImpl 继承时指定:
    // M = UserMapper, T = User
    // 所以基类中的 baseMapper 字段类型是 UserMapper
}

Spring 在创建 UserServiceImpl 的 Bean 时:

  1. 解析 UserServiceImpl 的泛型信息
  2. 发现它继承 ServiceImpl&lt;UserMapper, User&gt;
  3. 知道基类的 baseMapper 字段类型是 UserMapper
  4. 在容器中查找 UserMapper 类型的 Bean
  5. 注入到 baseMapper 字段

5. Spring 的泛型解析过程

5.1 Bean 定义解析

JAVA
// 1. 扫描时发现
@Component
public class UserServiceImpl extends ServiceImpl<UserMapper, User>

// 2. 创建 BeanDefinition
GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setBeanClass(UserServiceImpl.class);

// 3. 解析泛型父类
ResolvableType superType = ResolvableType.forClass(UserServiceImpl.class)
    .getSuperType();  // ServiceImpl<UserMapper, User>

// 4. 获取泛型参数
ResolvableType mapperType = superType.getGeneric(0);  // UserMapper
ResolvableType entityType = superType.getGeneric(1);   // User

5.2 依赖注入时的类型匹配

JAVA
// 在 DefaultListableBeanFactory 中
private Object resolveField(String fieldName, Object bean, BeanDefinition bd) {
    Field field = getField(fieldName, bean.getClass());
    
    // 获取字段的类型(包含泛型)
    ResolvableType fieldType = ResolvableType.forField(field, bean.getClass());
    // 对于 UserServiceImpl 的 baseMapper 字段:
    // fieldType = UserMapper
    
    // 在容器中查找匹配的 Bean
    String[] beanNames = getBeanNamesForType(fieldType);
    // 找到 UserMapper 的 Bean
    
    return getBean(beanNames[0]);
}

6. 泛型字段注入的限制

6.1 不能直接在泛型类中注入

JAVA
// ❌ 这不会工作
@Component
public class GenericService<T> {
    @Autowired
    private T repository;  // Spring 不知道 T 的具体类型
    
    public T getById(Long id) {
        return repository.findById(id);  // 编译错误
    }
}

6.2 需要具体化的子类

JAVA
// ✅ 通过子类具体化才能工作
public abstract class BaseRepository<T> {
    // 可以有非泛型字段
    @Autowired
    private DataSource dataSource;
}

// 具体子类
@Repository
public class UserRepository extends BaseRepository<User> {
    // 这里可以注入特定的依赖
    @Autowired
    private UserCache userCache;
}

7. 替代方案

7.1 使用方法注入

JAVA
@Component
public class GenericService<T> {
    
    // 通过方法注入具体类型
    protected T getRepository(Class<T> repositoryClass) {
        return applicationContext.getBean(repositoryClass);
    }
}

7.2 使用类型令牌模式

JAVA
@Component
public class GenericService<T> {
    private final T repository;
    
    public GenericService(Class<T> repositoryClass) {
        this.repository = applicationContext.getBean(repositoryClass);
    }
}

8. 完整示例

8.1 定义泛型 Repository

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

// 具体实现
@Repository
public class UserRepository implements BaseRepository<User, Long> {
    // 实现...
}

8.2 泛型 Service

JAVA
// 泛型 Service
public abstract class BaseService<R extends BaseRepository<T, ID>, T, ID> {
    
    @Autowired
    protected R repository;  // 这里能注入是因为子类具体化了 R
    
    public T getById(ID id) {
        return repository.findById(id);
    }
}

8.3 具体 Service

JAVA
@Service
public class UserService extends BaseService<UserRepository, User, Long> {
    // 自动注入 UserRepository
    // 因为父类的 R 被具体化为 UserRepository
    
    public void someMethod() {
        User user = getById(1L);  // 调用父类方法
    }
}

9. Spring 内部处理源码

9.1 字段注入源码

JAVA
// AutowiredAnnotationBeanPostProcessor
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, 
        Object bean, String beanName) {
    
    // 找到所有需要自动注入的字段
    InjectionMetadata metadata = findAutowiringMetadata(bean.getClass());
    
    // 注入字段
    metadata.inject(bean, beanName, pvs);
    
    return pvs;
}

// 在 inject 方法中
protected void inject(Object bean, @Nullable String beanName, 
        @Nullable PropertyValues pvs) throws Throwable {
    
    Field field = (Field) this.member;
    
    // 获取字段的类型(包含泛型)
    ResolvableType type = ResolvableType.forField(field, bean.getClass());
    
    // 根据类型查找 Bean
    Object value = beanFactory.resolveDependency(
        new DependencyDescriptor(field, true), beanName);
    
    // 反射设置字段值
    ReflectionUtils.makeAccessible(field);
    field.set(bean, value);
}

9.2 泛型类型解析

JAVA
// 解析字段类型的泛型信息
private ResolvableType getResolvableType(Field field, Object bean) {
    // 获取字段声明的类型
    ResolvableType type = ResolvableType.forField(field);
    
    // 如果字段是泛型,需要结合 bean 的实际类型
    if (type.hasGenerics()) {
        // 获取 bean 的实际类型
        ResolvableType beanType = ResolvableType.forClass(bean.getClass());
        
        // 解析字段在 bean 中的实际泛型类型
        type = type.as(beanType.getType());
    }
    
    return type;
}

10. 注意事项

10.1 泛型擦除的影响

JAVA
// 运行时,泛型信息可能丢失
public class ServiceImpl<M, T> {
    @Autowired
    private M mapper;  // 运行时类型是 Object
    
    // 但 Spring 在注入时能通过子类恢复泛型信息
}

10.2 多个候选 Bean 问题

JAVA
// 如果有多个相同泛型的实现
@Repository
public class UserRepository1 implements BaseRepository<User, Long> { }

@Repository
public class UserRepository2 implements BaseRepository<User, Long> { }

// 注入时会报错:有多个候选
// 需要添加 @Qualifier
public class UserService extends BaseService<UserRepository1, User, Long> {
    // 或者
    @Autowired
    @Qualifier("userRepository1")
    protected BaseRepository<User, Long> repository;
}

11. 总结

Spring 的泛型字段注入原理:

  1. 不是直接注入泛型参数:不能直接注入 @Autowired T mapper
  2. 需要具体化的子类:泛型字段必须在具体子类中确定具体类型
  3. 运行时泛型恢复:Spring 通过子类的泛型信息恢复父类字段的具体类型
  4. 依赖查找机制:Spring 在注入时会根据字段的类型(包含泛型)查找匹配的 Bean

关键理解

  • 泛型字段注入能够工作,是因为子类继承时提供了具体的类型参数
  • Spring 在创建 Bean 时,能解析类的继承结构中的泛型信息
  • 注入时,Spring 会根据字段在具体子类中的实际类型来查找 Bean

这就是为什么 MyBatis-Plus 的 ServiceImpl 中的 @Autowired protected M baseMapper 能够正常工作的原因。当有具体子类如 UserServiceImpl extends ServiceImpl&lt;UserMapper, User&gt; 时,Spring 知道 M 的具体类型是 UserMapper,然后注入 UserMapper 的 Bean。

全部评论: 0

    我有话说: