75 0

[作业] 手写简易Spring(五) [推广有奖]

  • 0关注
  • 0粉丝

等待验证会员

学前班

80%

还不是VIP/贵宾

-

威望
0
论坛币
0 个
通用积分
0
学术水平
0 点
热心指数
0 点
信用等级
0 点
经验
30 点
帖子
2
精华
0
在线时间
0 小时
注册时间
2018-11-10
最后登录
2018-11-10

楼主
山阴路的八楼 发表于 2025-12-1 14:40:48 |AI写论文

+2 论坛币
k人 参与回答

经管之家送您一份

应届毕业生专属福利!

求职就业群
赵安豆老师微信:zhaoandou666

经管之家联合CDA

送您一个全额奖学金名额~ !

感谢您参与论坛问题回答

经管之家送您两个论坛币!

+2 论坛币

前言

本期内容继续参考小傅哥的相关教程,聚焦于如何通过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框架始终坚持一种设计理念:

接口负责行为定义,抽象类封装共性逻辑,实现类只需关注具体业务细节。

这种设计极大提升了系统的可维护性与可扩展性,值得我们在日常开发中借鉴与应用。

二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

关键词:Spring Pring RING ING DEFINITIONS

您需要登录后才可以回帖 登录 | 我要注册

本版微信群
jg-xs1
拉您进交流群
GMT+8, 2026-1-6 23:54