mini-ssm Lab4 - 依赖注入(DI)

74 阅读3分钟

mini-ssm Lab4 - 依赖注入(DI)

mini-ssm github

在 Lab4 中,我们实现了依赖注入(DI)的基础功能,并支持两种注解:

  • @Autowired:自动装配并解决循环依赖。
  • @Value:从配置文件读取属性值并注入到指定字段中。

我们还调整了 getBean 方法,使其在实例化后立即完成参数注入,并解决循环依赖问题。

项目结构

  • Lab4 项目结构

    image.png

代码实现

在本部分,我们将主要展示 getBean 方法及 FieldProcessor 接口的实现。getBean 是获取 Bean 实例的核心方法,其中通过 populateBean 实现字段的自动注入。我们会使用两种字段处理器(AutowiredFieldProcessorValueFieldProcessor)完成不同的注解处理。

getBean 方法

getBean 中,新增了一个 earlyBeanMap 缓存,处理循环依赖问题。以下是核心实现代码:

@Override
public Object getBean(String beanName) throws InvocationTargetException, InstantiationException, IllegalAccessException {
    // 若是单例,直接从缓存中返回已存在的实例
    Object instance = factoryBeanInstanceCache.get(beanName);
    if (instance != null) {
        return instance;
    }
    if (earlyBeanMap.containsKey(beanName)) {
        return earlyBeanMap.get(beanName);
    }
    
    BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
    Assert.notNull(beanDefinition, "找不到 bean:" + beanName);
    
    // 实例化 Bean 并放入 IoC 容器
    Object o = instantiateBean(beanDefinition);
    
    // 注入字段依赖
    populateBean(o);
    
    factoryBeanInstanceCache.put(beanName, o);
    return o;
}
  • 缓存检查:首先检查单例缓存 factoryBeanInstanceCache 和早期缓存 earlyBeanMap,若找到实例则直接返回,避免重复实例化。
  • 实例化和依赖注入:若缓存中未找到,则实例化对象,并调用 populateBean 实现字段注入,将最终 Bean 存入单例缓存。

populateBean 方法

populateBean 方法用于注入字段依赖。它会遍历对象的所有字段,交给 FieldProcessor 处理相应的注解。

private void populateBean(Object o) throws InvocationTargetException, InstantiationException, IllegalAccessException {
    Field[] fields = o.getClass().getDeclaredFields();
    List<FieldProcessor> processors = getFieldProcessors();
    
    for (Field field : fields) {
        for (FieldProcessor fieldProcessor : processors) {
            String beanName = fieldProcessor.processField(o, field);
            if (beanName == null) continue;
            
            Object bean = getBean(beanName);
            boolean accessible = field.isAccessible();
            field.setAccessible(true);
            field.set(o, bean);
            field.setAccessible(accessible);
        }
    }
}
  • 字段遍历:遍历对象的每个字段。
  • 字段处理器调用:依次调用 FieldProcessor 实现类,处理符合条件的字段注解(如 @Autowired@Value)。
  • 注入 Bean 实例:当 FieldProcessor 返回了 Bean 名称时,通过 getBean 获取实例并注入字段。

FieldProcessor 接口

FieldProcessor 是一个通用接口,用于处理字段注解。它根据字段类型决定是否注入依赖或属性值。

public interface FieldProcessor {
    String processField(Object o, Field field);
}

返回值为 String,表示注入的 Bean 名称。若返回 null,则表示无需处理该字段。

@Autowired 循环依赖解决方案

BeanFactory 中加入了 earlyBeanMap 作为早期引用缓存,以解决循环依赖。具体流程如下:

  1. 提前暴露实例:在 instantiateBean 方法中,创建实例后先将其放入 earlyBeanMap
  2. 检测依赖:如果某个字段依赖的 Bean 在实例化过程中找不到,则从 earlyBeanMap 获取。
private final Map<String, Object> earlyBeanMap = new HashMap<>();

private Object instantiateBean(BeanDefinition beanDefinition) throws InvocationTargetException, InstantiationException, IllegalAccessException {
    // 实例化并将实例加入 earlyBeanMap
    Object instance = constructor.newInstance(args);
    earlyBeanMap.put(beanDefinition.getFactoryBeanName(), instance);
    return instance;
}
  • 举例说明:假设 A 和 B 相互依赖。
    • 实例化 A,将 A 放入 earlyBeanMap
    • 在 A 的依赖注入过程中发现 B,实例化 B 并将其放入 earlyBeanMap
    • 在 B 的依赖注入中发现 A,此时可以从 earlyBeanMap 获取到 A,从而完成循环依赖注入。

@Value 注解

@Value 注解的作用是从配置文件中读取值并注入到字段中。实现方式包括解析 YAMLproperties 文件,具体流程如下:

  1. 配置解析:读取配置并将其存储在静态映射中。
  2. 字段类型匹配:使用 Function 根据字段类型转换配置值。
public class ValueFieldProcessor implements FieldProcessor {
    private static final Map<Class<?>, Function<String, ?>> MAP = new HashMap<>();
    
    static {
        MAP.put(int.class, Integer::valueOf);
        MAP.put(Integer.class, Integer::valueOf);
        MAP.put(String.class, o -> o);
    }
    
    @Override
    public String processField(Object o, Field field) {
        if (!field.isAnnotationPresent(Value.class)) return null;
        Function<String, ?> function = MAP.get(field.getType());
        if (function == null) return null;
        
        Value value = field.getAnnotation(Value.class);
        String el = value.value();
        String parsing = ElInterpreter.parsing(el);
        
        try {
            boolean accessible = field.isAccessible();
            field.setAccessible(true);
            field.set(o, function.apply(parsing));
            field.setAccessible(accessible);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        return null;
    }
}
  • 解析和注入@Value 使用 ElInterpreter 解析表达式,将值转换并注入到字段中。