面向对象手写 Spring IoC - BeanDefinition 加载与解析

前文:
面向过程手写 Spring IoC
手写 Spring IoC 模块设计

手写 Spring IoC 容器 —— BeanDefinition 加载与解析

引言

在 Spring IoC 容器中,BeanDefinition 负责描述 Bean 的基本信息,如类名、作用域、初始化方法等。
本文先实现 Spring IoC 的核心模块之一:BeanDefinition 加载与解析。

主要功能

  • 资源加载:从 XML 文件中读取配置信息。
  • 解析 XML:解析 XML 文件内容并转换为 BeanDefinition 对象。
  • 注册 BeanDefinition:将解析后的 BeanDefinition 存储到 BeanDefinitionRegistry

1. 资源加载

Spring 允许从多种来源(如 Classpath、文件系统、网络等)加载 XML 配置文件。这里我们定义 Resource 接口,提供统一的资源访问方式。

1.1 资源接口

1
2
3
4
5
6
/
* 提供对资源的操作功能(资源可能存在于磁盘、网络、classpath等)
*/
public interface Resource {
InputStream getResource();
}

1.2 Classpath 资源实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ClasspathResource implements Resource {
private String location;

public ClasspathResource(String location) {
this.location = location;
}

@Override
public InputStream getResource() {
if (location.startsWith("classpath:")) {
location = location.substring(10);
}
return this.getClass().getClassLoader().getResourceAsStream(location);
}
}

设计思路:

  • Resource 作为抽象接口,提供不同的资源加载方式。
  • ClasspathResource 具体实现了从 类路径 加载资源的功能。

2. XML 解析

2.1 XML 解析器 XmlBeanDefinitionReader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class XmlBeanDefinitionReader {
private BeanDefinitionRegistry registry;

public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
this.registry = registry;
}

public void loadBeanDefinitions(Resource resource) {
InputStream inputStream = resource.getResource();
doLoadBeanDefinitions(inputStream);
}

private void doLoadBeanDefinitions(InputStream inputStream) {
// 创建 Document 文档对象
Document document = DocumentUtils.getDocument(inputStream);
BeanDefinitionDocumentReader documentReader = new DefaultBeanDefinitionDocumentReader(registry);
documentReader.registerBeanDefinitions(document.getRootElement());
}
}

设计思路:

  • XmlBeanDefinitionReader 负责解析 XML 配置文件。
  • loadBeanDefinitions 方法支持 Resource 作为参数,解耦资源的获取方式。
  • doLoadBeanDefinitions 通过 DocumentUtils 解析 XML,并委托 BeanDefinitionDocumentReader 进行解析。

3. BeanDefinition 解析

3.1 解析 XML 并注册 BeanDefinition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
private BeanDefinitionRegistry registry;

public DefaultBeanDefinitionDocumentReader(BeanDefinitionRegistry registry) {
this.registry = registry;
}

@Override
public void registerBeanDefinitions(Element rootElement) {
List<Element> elements = rootElement.elements();
for (Element element : elements) {
if ("bean".equals(element.getName())) {
parseDefaultElement(element);
} else {
parseCustomElement(element);
}
}
}

private void parseCustomElement(Element element) {
// 解析自定义标签(如 AOP、MVC 配置等)
}

private void parseDefaultElement(Element beanElement) {
try {
String id = beanElement.attributeValue("id");
String name = beanElement.attributeValue("name");
String clazzName = beanElement.attributeValue("class");
String initMethod = beanElement.attributeValue("init-method");
String scope = beanElement.attributeValue("scope", "singleton");

String beanName = id == null ? name : id;
Class<?> clazzType = Class.forName(clazzName);
beanName = beanName == null ? clazzType.getSimpleName() : beanName;

BeanDefinition beanDefinition = new BeanDefinition(clazzName, beanName);
beanDefinition.setInitMethod(initMethod);
beanDefinition.setScope(scope);

List<Element> propertyElements = beanElement.elements();
for (Element propertyElement : propertyElements) {
parsePropertyElement(beanDefinition, propertyElement);
}
registry.registerBeanDefinition(beanName, beanDefinition);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}

设计思路:

  • registerBeanDefinitions 遍历 XML 的 <bean> 标签。
  • parseDefaultElement 解析 bean 标签的属性,并转换为 BeanDefinition
  • parsePropertyElement 解析 property 标签。
  • registryBeanDefinitionRegistry,负责注册 BeanDefinition

3.2 解析 Bean 属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void parsePropertyElement(BeanDefinition beanDefinition, Element propertyElement) {
if (propertyElement == null) return;

String name = propertyElement.attributeValue("name");
String value = propertyElement.attributeValue("value");
String ref = propertyElement.attributeValue("ref");

if (value != null && ref != null) return;

PropertyValue pv = null;
if (value != null) {
TypedStringValue typedValue = new TypedStringValue(value);
Class<?> targetType = ReflectUtils.getTypeByFieldName(beanDefinition.getClazzName(), name);
typedValue.setTargetType(targetType);
pv = new PropertyValue(name, typedValue);
} else if (ref != null) {
pv = new PropertyValue(name, new RuntimeBeanReference(ref));
}
beanDefinition.addPropertyValue(pv);
}

设计思路:

  • value 属性表示直接赋值,ref 表示引用另一个 Bean。
  • TypedStringValue 解决 String 转换为具体类型的问题。
  • RuntimeBeanReference 处理 Bean 之间的依赖。