mini-ssm Lab4 - 依赖注入(DI)
在 Lab4 中,我们实现了依赖注入(DI)的基础功能,并支持两种注解:
- @Autowired:自动装配并解决循环依赖。
- @Value:从配置文件读取属性值并注入到指定字段中。
我们还调整了 getBean
方法,使其在实例化后立即完成参数注入,并解决循环依赖问题。
项目结构
-
Lab4 项目结构
代码实现
在本部分,我们将主要展示 getBean
方法及 FieldProcessor
接口的实现。getBean
是获取 Bean 实例的核心方法,其中通过 populateBean
实现字段的自动注入。我们会使用两种字段处理器(AutowiredFieldProcessor
和 ValueFieldProcessor
)完成不同的注解处理。
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
作为早期引用缓存,以解决循环依赖。具体流程如下:
- 提前暴露实例:在
instantiateBean
方法中,创建实例后先将其放入earlyBeanMap
。 - 检测依赖:如果某个字段依赖的 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,从而完成循环依赖注入。
- 实例化 A,将 A 放入
@Value
注解
@Value
注解的作用是从配置文件中读取值并注入到字段中。实现方式包括解析 YAML
或 properties
文件,具体流程如下:
- 配置解析:读取配置并将其存储在静态映射中。
- 字段类型匹配:使用
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
解析表达式,将值转换并注入到字段中。