前言
本期内容继续参考小傅哥的相关教程,聚焦于如何通过XML配置文件实现BeanDefinition的自动创建与注册。在上一阶段中,我们已经完成了对Bean属性的填充功能,但相关配置仍是在测试代码中手动完成的。而本阶段的核心目标是:
将BeanDefinition的定义过程完全交由XML文件管理,由Spring框架负责读取该XML文件,并基于其内容完成BeanDefinition的解析与注册。
设计思路分析
为了达成上述目标,我们需要从整体架构层面进行合理拆分与抽象,主要涉及以下几个关键点:
1. 需要构建一个专门用于读取并解析XML文件的组件,命名为 XmlBeanDefinitionReader。该组件需具备以下能力:
- 能够加载外部XML资源;
- 解析其中的Bean定义信息;
- 将解析后的结果注册为对应的BeanDefinition对象。
由于此前我们已定义了 BeanDefinitionRegistry 接口,并由 DefaultListableBeanFactory 类实现了该接口的功能,因此在XML解析完成后,可直接依赖此工厂类完成注册操作。由此可见,XmlBeanDefinitionReader 在实现时需要依赖具体的BeanFactory实例。
2. 关于资源获取方式的扩展性设计:不能局限于单一路径来源,应支持多种资源访问形式,包括但不限于:
- 类路径(ClassPath)下的配置文件;
- 本地文件系统(File)中的XML文件;
- 网络地址(HTTP/HTTPS)提供的远程资源。
为此,我们引入统一的资源抽象——Resource 接口,并由以下三个具体实现类分别处理不同类型的资源:
- ClassPathResource:处理类路径资源;
- FileSystemResource:处理本地文件系统资源;
- UrlResource:处理URL指向的远程资源。
所有实现均对外提供标准的 getInputStream() 方法,从而屏蔽底层差异,实现统一访问。
3. 进一步封装资源加载逻辑,定义 ResourceLoader 接口,并由核心实现类 DefaultResourceLoader 完成具体工作。该类对外暴露一个字符串类型的路径参数(path),内部根据路径前缀自动判断资源类型(如classpath:、file:、http://等),进而选择合适的Resource实现并返回对应的输入流。
系统流程示意如下:
接下来是各模块之间的依赖与继承关系图,有助于理解整体结构:
结合小傅哥的讲解,补充说明如下:
整个读取与注册流程中,采用了清晰的分层设计:
- BeanDefinitionReader:作为顶层接口,定义资源读取的基本行为规范;
- AbstractBeanDefinitionReader:抽象类,在接口基础上添加通用功能,如BeanDefinitionRegistry的持有和注册逻辑;
- XmlBeanDefinitionReader:最终实现类,专注于XML格式的解析与注册流程。
这种“接口定义 + 抽象共性 + 实现专注业务”的模式,使得各组件职责分明,扩展性强。
具体实现结构展示:
/**
* 资源加载部分的顶层父接口
*/
public interface Resource {
InputStream getInputStream() throws IOException;
}
Resource 接口定义了资源访问的标准方法。
public class ClassPathResource implements Resource {
private final String path;
private ClassLoader classLoader;
public ClassPathResource(String path) {
this(path, (ClassLoader) null);
}
public ClassPathResource(String path, ClassLoader classLoader) {
Assert.notNull(path, "Path must not be null");
this.path = path;
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}
@Override
public InputStream getInputStream() throws IOException {
InputStream is = classLoader.getResourceAsStream(path);
if (is == null) {
throw new FileNotFoundException(
this.path + "cannot be opened because it does not exist");
}
return is;
}
}
ClassPathResource 实现从类路径加载资源。
public class FileSystemResource implements Resource {
// 文件
private final File file;
// 路径
private final String path;
// 两种可能, 一种是给文件, 那么直接调用getPath获取路径
public FileSystemResource(File file) {
this.file = file;
this.path = file.getPath();
}
// 一种可能是只给路径, 那就在这个路径上新建一个文件
public FileSystemResource(String path) {
this.file = new File(path);
this.path = path;
}
public String getPath() {
return path;
}
@Override
public InputStream getInputStream() throws IOException {
return new FileInputStream(this.file);
}
}
FileSystemResource 负责从本地文件系统读取资源。
public class UrlResource implements Resource {
private final URL url;
public UrlResource(URL url) {
Assert.notNull(url, "Url must not be null");
this.url = url;
}
@Override
public InputStream getInputStream() throws IOException {
// 打开连接
URLConnection con = this.url.openConnection();
try {
return con.getInputStream();
} catch (IOException ex) {
if (con instanceof HttpURLConnection) {
// 如果是Http请求, 就调用disconnect取消连接
((HttpURLConnection) con).disconnect();
} throw ex;
}
}
}
UrlResource 支持通过网络URL获取远程资源。
public interface ResourceLoader {
// 按照资源加载的不同方式,资源加载器可以把这些方式集中到统一的类服务下进行处理,
// 外部用户只需要传递资源地址即可,简化使用。
String CLASSPATH_URL_PREFIX = "classpath:";
Resource getResource(String location);
}
ResourceLoader 接口定义资源加载的统一入口。
/**
* 这是资源加载器的核心实现, 用于返回定义好的Resource类
*/
public class DefaultResourceLoader implements ResourceLoader {
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
if (location.startsWith(CLASSPATH_URL_PREFIX)) {
// 注意这里截取的是前缀之后的部分
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()));
}
else {
try {
// 如果不是ClassPath, 就尝试变成URL, 如果抛出错误了, 就代表是FileSystem
URL url = new URL(location);
return new UrlResource(url);
} catch (MalformedURLException e) {
return new FileSystemResource(location);
}
}
}
}
DefaultResourceLoader 是资源加载器的默认实现,负责根据路径自动匹配资源类型。
/**
* 解析SPringXml文件的顶层接口
*/
public interface BeanDefinitionReader {
BeanDefinitionRegistry getRegistry();
ResourceLoader getResourceLoader();
void loadBeanDefinitions(Resource resource) throws BeansException;
void loadBeanDefinitions(Resource... resource) throws BeansException;
void loadBeanDefinitions(String location) throws BeansException;
}
BeanDefinitionReader 接口声明Bean定义读取的基本功能。
/**
* 解析资源部分的抽象类
* 实现顶层接口中的 getRegistry 和 getResourceLoader 方法
*/
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {
private final BeanDefinitionRegistry registry;
private ResourceLoader resourceLoader;
protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
this(registry, new DefaultResourceLoader());
}
public AbstractBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {
this.registry = registry;
this.resourceLoader = resourceLoader;
}
@Override
public BeanDefinitionRegistry getRegistry() {
return registry;
}
@Override
public ResourceLoader getResourceLoader() {
return resourceLoader;
}
}
AbstractBeanDefinitionReader 提供部分通用实现,简化子类开发。
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
super(registry);
}
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {
super(registry, resourceLoader);
}
// 顶层父类定义的规则, 是核心方法
@Override
public void loadBeanDefinitions(Resource resource) throws BeansException {
try {
try (InputStream inputStream = resource.getInputStream()) {
doLoadBeanDefinitions(inputStream);
}
} catch (IOException | ClassNotFoundException e) {
throw new BeansException("IOException parsing XML document from " + resource, e);
}
}
@Override
public void loadBeanDefinitions(Resource... resources) throws BeansException {
for (Resource resource : resources) {
loadBeanDefinitions(resource);
}
}
@Override
public void loadBeanDefinitions(String location) throws BeansException {
ResourceLoader resourceLoader = getResourceLoader();
Resource resource = resourceLoader.getResource(location);
loadBeanDefinitions(resource);
}
// 根据资源Resource的输入流生成BeanDefinition
protected void doLoadBeanDefinitions(InputStream inputStream) throws ClassNotFoundException {
Document doc = XmlUtil.readXML(inputStream);
Element root = doc.getDocumentElement();
NodeList childNodes = root.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
// 判断元素
if (!(childNodes.item(i) instanceof Element)) continue;
// 判断对象
if (!"bean".equals(childNodes.item(i).getNodeName())) continue;
// 解析标签
Element bean = (Element) childNodes.item(i);
String id = bean.getAttribute("id");
String name = bean.getAttribute("name");
String className = bean.getAttribute("class");
// 获取 Class,方便获取类中的名称
Class<?> clazz = Class.forName(className);
// 优先级 id > name
String beanName = StrUtil.isNotEmpty(id) ? id : name;
if (StrUtil.isEmpty(beanName)) {
beanName = StrUtil.lowerFirst(clazz.getSimpleName());
}
// 定义Bean
BeanDefinition beanDefinition = new BeanDefinition(clazz);
// 读取属性并填充
// 这里是准备获取<bean></bean>内部的ChildNode
for (int j = 0; j < bean.getChildNodes().getLength(); j++) {
if (!(bean.getChildNodes().item(j) instanceof Element)) continue;
if (!"property".equals(bean.getChildNodes().item(j).getNodeName())) continue;
// 解析标签:property
Element property = (Element) bean.getChildNodes().item(j);
String attrName = property.getAttribute("name");
String attrValue = property.getAttribute("value");
String attrRef = property.getAttribute("ref");
// 获取属性值:引入对象、值对象
Object value = StrUtil.isNotEmpty(attrRef) ? new BeanReference(attrRef) : attrValue;
// 创建属性信息
PropertyValue propertyValue = new PropertyValue(attrName, value);
beanDefinition.getPropertyValues().addPropertyValue(propertyValue);
}
if (getRegistry().containsBeanDefinition(beanName)) {
throw new BeansException("Duplicate beanName[" + beanName + "] is not allowed");
}
// 注册 BeanDefinition
getRegistry().registerBeanDefinition(beanName, beanDefinition);
}
}
}
XmlBeanDefinitionReader 最终完成XML文件的解析与BeanDefinition注册。
总结
通过逐步实现上述模块,可以发现整个流程虽然层次较多,但逻辑清晰、结构严谨。建议读者在学习过程中,使用纸笔绘制类图与调用流程,有助于加深理解。
回顾这几期的内容,不难看出Spring框架始终坚持一种设计理念:
接口负责行为定义,抽象类封装共性逻辑,实现类只需关注具体业务细节。
这种设计极大提升了系统的可维护性与可扩展性,值得我们在日常开发中借鉴与应用。


雷达卡


京公网安备 11010802022788号







