[源码系列:手写spring] IOC第七节:加载xml文件中定义的Bean
目录
主要内容
上一节加入了资源管理器,令我们的框架具备了读取配置文件的能力。在该篇文章中将为我们的框架加入xml格式的配置文件,在配置文件中声明式的定义bean信息,利用资源加载器读取xml配置文件,进而解析出bean信息,注入进bean容器。
BeanDefinitionReader:读取bean定义信息的接口,获取资源后,读取bean定义信息,生成BeanDefinition注册入容器。
AbstractBeanDefinitionReader:BeanDefinitionReader的抽象实现类,从需要实现的抽象类BeanDefinitionReader功能上看,该类需要拥有 ResourceLoader 和 BeanDefinitionRegistry 两个属性。
XmlBeanDefinitionReader:实现从xml中读取和加载Bean定义信息。
由于xml文件中读取的都是文本,本章节bean属性暂时只支持String类型和引用其他bean,后面会讲解类型转换器,实现类型转换。

注意:本章对BeanFactory的体系做了调整。
代码分支
除去添加的核心代码外,其他地方也有非常细微的改动,想要测试代码可行性的同学可以查看该分支下具体代码。
xml-file-define-bean 加载XML资源中的Bean剖析Spring源码,包括常见特性IOC、AOP、三级缓存等... Contribute to yihuiaa/little-spring development by creating an account on GitHub.
https://github.com/yihuiaa/little-spring/tree/xml-file-define-bean
核心代码
BeanDefinitionReader
定义加载资源并且读取Bean定义信息的接口方法,包括方法有:获取Bean注册表、获取资源加载器、从静态资源读取BeanDefinition,根据路径加载BeanDefinition。
Reader中加载的含义:reader的加载目标是资源下的Bean,不仅是read资源,还包括load Bean,registe Bean,即读取并注册,利用ResourceLoader加载资源后,读取Bean定义信息并注册到注册表。
public interface BeanDefinitionReader {
/**
* 获取注册表
* @return
*/
BeanDefinitionRegistry getRegistry();
/**
* 获取资源加载器
* @return
*/
ResourceLoader getResourceLoader();
void loadBeanDefinitions(Resource resource) throws BeansException;
void loadBeanDefinitions(String location) throws BeansException;
void loadBeanDefinitions(String[] locations) throws BeansException;
}
AbstractBeanDefinitionReader
BeanDefinitionReader的抽象实现类
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader{
private final BeanDefinitionRegistry registry;
private ResourceLoader resourceLoader;
protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
this(registry, new DefaultResourceLoader());
}
@Override public BeanDefinitionRegistry getRegistry() {
return this.registry;
}
public AbstractBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {
this.registry = registry;
this.resourceLoader = resourceLoader;
}
@Override public ResourceLoader getResourceLoader() {
return resourceLoader;
}
@Override public void loadBeanDefinitions(String[] locations) throws BeansException {
for (String location : locations) {
loadBeanDefinitions(location);
}
}
}
XmlBeanDefinitionReader
定义了一些字符常量表示xml节点名称,实现两个loadBeanDefinitions(),实现xml中BeanDefinition的读取和加载
package org.springframework.beans.factory.xml;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.XmlUtil;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.core.io.Resource;
import org.springframework.beans.core.io.ResourceLoader;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import java.io.IOException;
import java.io.InputStream;
/**
* ● @author: YiHui
* ● @date: Created in 17:41 2023/4/9
*/
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
public static final String BEAN_ELEMENT = "bean";
public static final String PROPERTY_ELEMENT = "property";
public static final String ID_ATTRIBUTE = "id";
public static final String NAME_ATTRIBUTE = "name";
public static final String CLASS_ATTRIBUTE = "class";
public static final String VALUE_ATTRIBUTE = "value";
public static final String REF_ATTRIBUTE = "ref";
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
super(registry);
}
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {
super(registry, resourceLoader);
}
@Override public void loadBeanDefinitions(String location) throws BeansException {
ResourceLoader resourceLoader = getResourceLoader();
Resource resource = resourceLoader.getResource(location);
this.loadBeanDefinitions(resource);
}
@Override public void loadBeanDefinitions(Resource resource) throws BeansException {
try {
InputStream inputStream = resource.getInputStream();
doLoadBeanDefinitions(inputStream);
}catch (IOException e) {
throw new BeansException("IOException parsing XML document from " + resource, e);
}
}
private void doLoadBeanDefinitions(InputStream inputStream){
Document document = XmlUtil.readXML(inputStream);
Element rootElement = document.getDocumentElement();
NodeList childNodes = rootElement.getChildNodes();
for (int i = 0; i < childNodes.getLength();i++) {
if(childNodes.item(i) instanceof Element){
if(BEAN_ELEMENT.equals(childNodes.item(i).getNodeName())){
//解析Bean标签
Element bean = (Element) childNodes.item(i);
String id = bean.getAttribute(ID_ATTRIBUTE);
String name = bean.getAttribute(NAME_ATTRIBUTE);
String className = bean.getAttribute(CLASS_ATTRIBUTE);
Class<?> clazz = null;
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new BeansException("Cannot find class [" + className + "]");
}
//id优先于name
String beanName = StrUtil.isNotEmpty(id) ? id : name;
if (StrUtil.isEmpty(beanName)) {
//如果id和name都为空,将类名的第一个字母转为小写后作为bean的名称
beanName = StrUtil.lowerFirst(clazz.getSimpleName());
}
BeanDefinition beanDefinition = new BeanDefinition(clazz);
//Bean属性
for(int j = 0; j < bean.getChildNodes().getLength(); j++) {
if(PROPERTY_ELEMENT.equals(bean.getChildNodes().item(j).getNodeName())){
//解析property标签
Element property = (Element) bean.getChildNodes().item(j);
String nameAttribute = property.getAttribute(NAME_ATTRIBUTE);
String valueAttribute = property.getAttribute(VALUE_ATTRIBUTE);
String refAttribute = property.getAttribute(REF_ATTRIBUTE);
if (StrUtil.isEmpty(nameAttribute)) {
throw new BeansException("The name attribute cannot be null or empty");
}
Object value = valueAttribute;
if (StrUtil.isNotEmpty(refAttribute)) {
value = new BeanReference(refAttribute);
}
PropertyValue propertyValue = new PropertyValue(nameAttribute,value);
beanDefinition.getPropertyValues().addPropertyValue(propertyValue);
}
}
if (getRegistry().containsBeanDefinition(beanName)) {
//beanName不能重名
throw new BeansException("Duplicate beanName[" + beanName + "] is not allowed");
}
//注册BeanDefinition
getRegistry().registerBeanDefinition(beanName, beanDefinition);
} }
}
}
}
测试
bean定义文件spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<bean id="person" class="bean.Person">
<property name="name" value="yiHui"/>
<property name="car" ref="car"/>
</bean>
<bean id="car" class="bean.Car">
<property name="name" value="Rolls-Royce"/>
</bean>
</beans>
Java代码
@Test
public void testXmlFile() throws Exception {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.loadBeanDefinitions("classpath:spring.xml");
Person person = (Person) beanFactory.getBean("person");
System.out.println(person);
assertThat(person.getName()).isEqualTo("derek");
assertThat(person.getCar().getBrand()).isEqualTo("porsche");
Car car = (Car) beanFactory.getBean("car");
System.out.println(car);
assertThat(car.getBrand()).isEqualTo("porsche");
}
}
测试结果
Person{name='yiHui', age='null', car=Car{name='Rolls-Royce'}}
[源码系列:手写spring] IOC第七节:加载xml文件中定义的Bean的更多相关文章
- 2.3 spring5源码系列---内置的后置处理器PostProcess加载源码
本文涉及主题 1. BeanFactoryPostProcessor调用过程源码剖析 2. 配置类的解析过程源码 3. 配置类@Configuration加与不加的区别 4. 重复beanName的覆 ...
- Spring源码分析 手写简单IOC容器
Spring的两大特性就是IOC和AOP. IOC Container,控制反转容器,通过读取配置文件或注解,将对象封装成Bean存入IOC容器待用,程序需要时再从容器中取,实现控制权由程序员向程序的 ...
- 框架源码系列六:Spring源码学习之Spring IOC源码学习
Spring 源码学习过程: 一.搞明白IOC能做什么,是怎么做的 1. 搞明白IOC能做什么? IOC是用为用户创建.管理实例对象的.用户需要实例对象时只需要向IOC容器获取就行了,不用自己去创建 ...
- 《四 spring源码》手写springioc框架
手写SpringIOCXML版本 /** * 手写Spring专题 XML方式注入bean * * * */ public class ClassPathXmlApplicationContext { ...
- 利用递归,反射,注解等,手写Spring Ioc和Di 底层(分分钟喷倒面试官)了解一下
再我们现在项目中Spring框架是目前各大公司必不可少的技术,而大家都知道去怎么使用Spring ,但是有很多人都不知道SpringIoc底层是如何工作的,而一个开发人员知道他的源码,底层工作原理,对 ...
- Spring 源码 (2)Spring IOC 容器 前戏准备工作
Spring 最重要的方法refresh方法 根据上一篇文章 https://www.cnblogs.com/redwinter/p/16141285.html Spring Bean IOC 的创建 ...
- 框架源码系列十:Spring AOP(AOP的核心概念回顾、Spring中AOP的用法、Spring AOP 源码学习)
一.AOP的核心概念回顾 https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/core.html#a ...
- 框架源码系列八:Spring源码学习之Spring核心工作原理(很重要)
目录:一.搞清楚ApplicationContext实例化Bean的过程二.搞清楚这个过程中涉及的核心类三.搞清楚IOC容器提供的扩展点有哪些,学会扩展四.学会IOC容器这里使用的设计模式五.搞清楚不 ...
- Spring 源码 (1)Spring IOC Bean 创建的整体流程
Spring IOC 中涉及的重要接口 BeanDefinition Bean的描述信息,实现类包括 RootBeanDefinition 和 GenericBeanDefinition,Bean的描 ...
- 鸿蒙内核源码分析(进程镜像篇)|ELF是如何被加载运行的? | 百篇博客分析OpenHarmony源码 | v56.01
百篇博客系列篇.本篇为: v56.xx 鸿蒙内核源码分析(进程映像篇) | ELF是如何被加载运行的? | 51.c.h.o 加载运行相关篇为: v51.xx 鸿蒙内核源码分析(ELF格式篇) | 应 ...
随机推荐
- clip-retrieval检索本地数据集
clip-retrieval检索本地数据集 from clip_retrieval.clip_client import ClipClient, Modality from tqdm import t ...
- JVM实战—7.如何模拟GC场景并阅读GC日志
大纲 1.动手模拟出频繁Young GC的场景 2.JVM的Young GC日志应该怎么看 3.代码模拟动态年龄判定规则进入老年代 4.代码模拟S区放不下部分进入老年代 5.JVM的Full GC日志 ...
- 编译树莓派Linux内核
1.建议边看视频边跟着教程走 https://www.bilibili.com/video/av91990721?zw 2.准备工作 下载官方提供的交叉编译工具链 git clone https:// ...
- 场景题:假设有40亿QQ号,但只有1G内存,如何实现去重?
当数据量比较大时,使用常规的方式来判重就不行了.例如,使用 MySQL 数据库判重,或使用 List.contains() 或 Set.contains() 判重就不行了,因为数据量太大会导致内存放不 ...
- springboot starter 原理解析及实践
什么是springboot starter starter是springBoot的一个重要部分.通过starter,我们能够快速的引入一个功能,而无需额外的配置.同时starter一般还会给我提供预留 ...
- Netty5 服务端和客户端-copy
概述netty 5 已经放弃掉了,作为学习netty4和5的差别不大,本例子是基于netty5 https://github.com/netty/netty/issues/4466 线程安全一个thr ...
- java多线程---总结(2)
ThreadPoolExecutor 官方API解释线程池的好处: (1)通过重用线程池中的线程,来减少每个线程创建和销毁的性能开销. (2)对线程进行一些维护和管理,比如定时开始,周期执行,并发数控 ...
- 推荐几个不错的 Linux 服务器管理工具
前言 选择一款好的 Linux 服务器管理工具能够极大地提高运维效率,保障业务连续性.今天大姚给大家分享3款不错的 Linux 服务器管理工具,希望可以帮助到有需要的同学. 1Panel 1Panel ...
- 使用 pdf.js 通过文件流方式加载pdf文件
关于Pdf.js的基础知识,请参考我的博客 使用 pdf.js 在网页中加载 pdf 文件 使用 pdf.js 跨域问题的处理方法 上面两篇博客中介绍的内容都是基于直接加载远程服务器中静态PDF文件 ...
- oracle配置SGA参数不当导致不能正确启动数据库实例处理
原因:生成环境数据库想要增加数据库内存配置参数SGA_TARGET增加到42G,但是没有配置SGA_MAX_SIZE参数值,导致SHUTDOWN IMMEDIATE停止数据库,再STARTUP启动数据 ...