Spring IOC源码篇六 核心方法obtainFreshBeanFactory.parseCustomElement

BeanDefinitionParserDelegate.parseCustomElement

1.写在前面

本篇非常重要,主要是分析标签解析原理

2.准备工作

bean类

java 复制代码
/**
 * bean类
 */
public class Test {

    private String username;

    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

启动类

java 复制代码
/**
 * 启动类
 */
public class Main {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("application-beans.xml");
        Test test = (Test) context.getBean("test");
        System.out.println("test = " + test);
        System.out.println("test username = " + test.getUsername());
        System.out.println("test password = " + test.getPassword());
    }
}

application-beans.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
			
       xmlns:contect="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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.xsd">

    <contect:property-placeholder location="classpath:database.properties"/>

    <bean id="test" class="com.sctelcp.bigdata.spring.Test">
        <property name="id" value="1"/>
        <property name="name" value="yuriy"/>
    </bean>

    <alias name="test" alias="yuriy-test"/>
</beans>

database.properties

properties 复制代码
jdbc.username=root
jdbc.password=root

改动点说明:

1.新增xml namespace:

xmlns:contect="http://www.springframework.org/schema/context"

2.新增xml schemaLocation:

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context.xsd

3.新增自定义标签配置:

<contect:property-placeholder location="classpath:database.properties"/>

3.前置知识:XML 约束规范

在 Spring 框架中,XSD(XML Schema Definition)和 DTD(Document Type Definition)是两种用于约束 XML 配置文件结构的规范。它们定义了 XML 文档中允许的元素、属性、嵌套关系等,确保配置文件的合法性。Spring 早期主要使用 DTD,后期逐渐转向 XSD,两者在功能和使用上有显著区别。

1.DTD (Document Type Definition):

Spring 早期版本(如 2.0 及之前)的 XML 配置主要依赖 DTD 约束,例如:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//Spring//DTD Bean 2.0//EN"
        "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
    <bean id="userService" class="com.example.UserService">
        <property name="userDao" ref="userDao"/>
    </bean>
    <bean id="userDao" class="com.example.UserDao"/>
</beans>

2.XSD (XML Schema Definition)

Spring 2.0 及之后版本引入 XSD,逐步替代 DTD 成为主要约束方式,尤其在支持命名空间扩展后(如 context、aop 等命名空间)。通过 xmlns 定义命名空间(如 xmlns:context 绑定 context 前缀与 http://www.springframework.org/schema/context URI)。通过 xsi:schemaLocation 绑定命名空间 URI 与对应的 XSD 文件地址(命名空间 URI=XSD 路径)。可以看到配置的xmlns和schemaLocation是一个网络地址。但是实际工作中我们在没有网络的情况下也可以开发。下面代码中会详解原理
核心的XSD文件:

spring-beans.xsd:定义基础 、 等标签。

spring-context.xsd:定义 context:component-scan、context:annotation-config 等标签例如:

xml 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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.xsd">

    <context:property-placeholder location="classpath:database.properties"/>

    <bean id="test" class="com.sctelcp.bigdata.spring.Test">
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <alias name="test" alias="yuriy-test"/>
</beans>

4.BeanDefinitionParserDelegate.parseCustomElement方法

开始解析自定义标签context

Let's go

java 复制代码
/**
 * 根据自定义命名空间标签的命名空间 URI,找到对应的 NamespaceHandler
 * (命名空间处理器),并委托处理器解析标签内容,生成 BeanDefinition
 * (或完成其他配置逻辑)
 */
public BeanDefinition parseCustomElement(Element ele) {
	// 调用重载方法
	return parseCustomElement(ele, null);
}

/**
 * 方法重载
 */
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
	// 获取自定义标签的命名空间URI
	String namespaceUri = getNamespaceURI(ele);
	if (namespaceUri == null) {
		return null;
	}
	// 根据URI查找对应的NamespaceHandler(命名空间处理器),这里很重要,查看第五点详解
	NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
	if (handler == null) {
		error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
		return null;
	}
	// 委托处理器解析标签,返回生成的BeanDefinition
	return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);

这里详细说明一下readerContext参数和getNamespaceHandlerResolver()是哪里被赋值的
readerContext:

java 复制代码
/**
 * XmlBeanDefinitionReader.registerBeanDefinitions方法中
 * createReaderContext(resource)创建了readerContext
 */
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
	BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
	int countBefore = getRegistry().getBeanDefinitionCount();
	documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
	return getRegistry().getBeanDefinitionCount() - countBefore;
}

/**
 * XmlBeanDefinitionReader.createReaderContext方法
 */
public XmlReaderContext createReaderContext(Resource resource) {
	return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
			this.sourceExtractor, this, getNamespaceHandlerResolver());
}

/**
 * XmlBeanDefinitionReader.getNamespaceHandlerResolver方法
 * createDefaultNamespaceHandlerResolver()创建了DefaultNamespaceHandlerResolver
 */
public NamespaceHandlerResolver getNamespaceHandlerResolver() {
	if (this.namespaceHandlerResolver == null) {
		this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
	}
	return this.namespaceHandlerResolver;
}

5.DefaultNamespaceHandlerResolver.resolve方法

java 复制代码
/**
 * 根据传入的命名空间 URI,从预加载的映射关系中找到对应的 NamespaceHandler 
 * 实例(或其类名),完成实例化、初始化后返回。
 */
public NamespaceHandler resolve(String namespaceUri) {
	// 获取命名空间URI与处理器的映射关系(从spring.handlers加载)
	Map<String, Object> handlerMappings = getHandlerMappings();
	// 根据URI从映射中获取处理器对象或类名
	Object handlerOrClassName = handlerMappings.get(namespaceUri);
	if (handlerOrClassName == null) {
		// 无对应处理器,返回null
		return null;
	}
	else if (handlerOrClassName instanceof NamespaceHandler) {
		// 无对应处理器,返回null
		return (NamespaceHandler) handlerOrClassName;
	}
	else {
		// 若为类名(首次加载),实例化并初始化处理器
		String className = (String) handlerOrClassName;
		try {
			// 加载处理器类
			Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
			// 校验是否实现了NamespaceHandler接口
			if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
				throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
						"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
			}
			// 实例化处理器
			NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
			// 初始化处理器(注册标签解析器)
			namespaceHandler.init();
			// 缓存实例,避免重复创建
			handlerMappings.put(namespaceUri, namespaceHandler);
			return namespaceHandler;
		}
		catch (ClassNotFoundException ex) {
			throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
					"] for namespace [" + namespaceUri + "]", ex);
		}
		catch (LinkageError err) {
			throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
					className + "] for namespace [" + namespaceUri + "]", err);
		}
	}
}

6.DefaultNamespaceHandlerResolver.getHandlerMappings方法

java 复制代码
/**
 * 懒加载并缓存 META-INF/spring.handlers 文件中的配置,
 * 返回 "命名空间 URI→处理器类名" 的映射关系。
 */
private Map<String, Object> getHandlerMappings() {
	// 先从缓存获取映射关系
	Map<String, Object> handlerMappings = this.handlerMappings;
	// 这里说个问题,当大家debug到这里的时候handlerMappings已经不为null了,这是因为idea debug
	// 模式调用了该类的toString方法,toString方法调用了当前方法已经加载过了
	if (handlerMappings == null) {
		// 双重检查锁:确保多线程环境下仅加载一次
		synchronized (this) {
			handlerMappings = this.handlerMappings;
			if (handlerMappings == null) {
				if (logger.isTraceEnabled()) {
					logger.trace("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]");
				}
				try {
					// 加载类路径下所有META-INF/spring.handlers文件
					// this.handlerMappingsLocation在DefaultNamespaceHandlerResolver构造器中被赋值的值就是当前类的
					// 常量DEFAULT_HANDLER_MAPPINGS_LOCATION,加载地址:META-INF/spring.handlers
					Properties mappings =
							PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
					if (logger.isTraceEnabled()) {
						logger.trace("Loaded NamespaceHandler mappings: " + mappings);
					}
					// 将Properties转换为ConcurrentHashMap(线程安全)
					handlerMappings = new ConcurrentHashMap<>(mappings.size());
					CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
					// 缓存映射关系,避免重复加载
					this.handlerMappings = handlerMappings;
				}
				catch (IOException ex) {
					throw new IllegalStateException(
							"Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
				}
			}
		}
	}
	return handlerMappings;
}

7.PropertiesLoaderUtils.loadAllProperties方法

java 复制代码
/**
 * 从类路径下查找所有名称为 resourceName 的资源文件(可能分布在不同 JAR 包中),
 * 读取并合并其内容为一个 Properties 对象。
 */
public static Properties loadAllProperties(String resourceName, @Nullable ClassLoader classLoader) throws IOException {
	Assert.notNull(resourceName, "Resource name must not be null");
	// 确定类加载器(优先使用传入的classLoader,否则使用默认类加载器)
	ClassLoader classLoaderToUse = classLoader;
	if (classLoaderToUse == null) {
		classLoaderToUse = ClassUtils.getDefaultClassLoader();
	}
	// 查找类路径下所有名为resourceName的资源(可能来自多个JAR包)
	Enumeration<URL> urls = (classLoaderToUse != null ? classLoaderToUse.getResources(resourceName) :
			ClassLoader.getSystemResources(resourceName));
	// 初始化Properties对象,用于合并所有资源内容
	Properties props = new Properties();
	// 遍历所有找到的资源,读取并合并内容
	while (urls.hasMoreElements()) {
		// 获取资源的URL(如jar:file:/xxx/spring-context.jar!/META-INF/spring.handlers)
		URL url = urls.nextElement();
		// 打开资源连接
		URLConnection con = url.openConnection();
		// 根据是否允许缓存设置连接属性
		ResourceUtils.useCachesIfNecessary(con);
		// 读取资源内容(支持Properties和XML格式)
		try (InputStream is = con.getInputStream()) {
			// XML格式(.xml)
			if (resourceName.endsWith(XML_FILE_EXTENSION)) {
				if (shouldIgnoreXml) {
					throw new UnsupportedOperationException("XML support disabled");
				}
				// 从XML加载配置
				props.loadFromXML(is);
			}
			else {
				// 普通Properties格式(.properties)从流加载配置
				props.load(is);
			}
		}
	}
	return props;
}

这里我给出spring-context jar包中的样列

8.ContextNamespaceHandler.init方法

这里我们回到第五步 namespaceHandler.init();这行。记住我们当前debug是在解析context的自定义标签

<context:property-placeholder location="classpath:database.properties"/>

第五步 中handlerMappings已经加载了所有的命名空间URI和处理类的映射关系了。在第五步的Object handlerOrClassName = handlerMappings.get(namespaceUri);中获取context标签命名空间对应的处理类ContextNamespaceHandler。然后调用namespaceHandler.init();完成handler的初始化。这里我们看下初始化具体做了哪些事儿。其他的标签解析步骤同样如此

java 复制代码
/**
 * 注册 context 命名空间下所有标签的解析器,建立 "标签名称→解析器" 的映射关系。
 * 当 Spring 解析 XML 中 <context:component-scan>、<context:annotation-config> 等标签时,
 * 会根据该映射找到对应的解析器,执行具体的解析逻辑(如扫描包、注册 Bean 定义等)。
 * 这里我们可以看到我们比较熟悉的两个标签property-placeholder和component-scan
 */
public void init() {
	// 调用父类方法,注册<context:property-placeholder>标签的解析器
	registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
	// 调用父类方法,注册<context:property-override>标签的解析器
	registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
	// 调用父类方法,注册<context:annotation-config>标签的解析器
	registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
	// 调用父类方法,注册<context:component-scan>标签的解析器
	registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
	// 调用父类方法,注册<context:load-time-weaver>标签的解析器
	registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
	// 调用父类方法,注册<context:spring-configured>标签的解析器
	registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
	// 调用父类方法,注册<context:mbean-export>标签的解析器
	registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
	// 调用父类方法,注册<context:mbean-server>标签的解析器
	registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}

/**
 * 可以看到就是将映射关系存放到父类的parsers中
 * private final Map<String, BeanDefinitionParser> parsers = new HashMap<>();
 */
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
	this.parsers.put(elementName, parser);
}

9.ContextNamespaceHandler.parse方法

回到第四步的

return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));

这里的handler就是ContextNamespaceHandler

PropertyPlaceholderBeanDefinitionParser类图

java 复制代码
/**
 * 这里再次贴处理我们当前的标签
 * <context:property-placeholder location="classpath:database.properties"/>
 */
public BeanDefinition parse(Element element, ParserContext parserContext) {
	// 这里就是获取对应标签(property-placeholder)的解析器,
	// 这里我们获取到的是第八步中注册的PropertyPlaceholderBeanDefinitionParser请参考上图的类图关系
	BeanDefinitionParser parser = findParserForElement(element, parserContext);
	return (parser != null ? parser.parse(element, parserContext) : null);
}

/**
 * 这里来到了AbstractBeanDefinitionParser.parse
 */
public final BeanDefinition parse(Element element, ParserContext parserContext) {
	// 调用子类实现的parseInternal,解析标签生成BeanDefinition(核心逻辑由子类实现)第十步详解有
	AbstractBeanDefinition definition = parseInternal(element, parserContext);
	// 非嵌套标签(顶级标签)且解析成功时,执行注册流程
	if (definition != null && !parserContext.isNested()) {
		try {
			// 解析或生成Bean的ID
			String id = resolveId(element, definition, parserContext);
			if (!StringUtils.hasText(id)) {
				parserContext.getReaderContext().error(
						"Id is required for element '" + parserContext.getDelegate().getLocalName(element)
								+ "' when used as a top-level tag", element);
			}
			// 处理别名(若需要从name属性解析)
			String[] aliases = null;
			if (shouldParseNameAsAliases()) {
				// 从name属性获取别名(逗号分隔)
				String name = element.getAttribute(NAME_ATTRIBUTE);
				if (StringUtils.hasLength(name)) {
					aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
				}
			}
			// 封装为BeanDefinitionHolder(包含定义、ID、别名)
			BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
			// 注册BeanDefinition到容器,解析标签最终都是将其生成bean定义信息并注册到bean定义容器中
			registerBeanDefinition(holder, parserContext.getRegistry());
			// 发布组件注册事件(可选)
			if (shouldFireEvents()) {
				BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
				postProcessComponentDefinition(componentDefinition);
				parserContext.registerComponent(componentDefinition);
			}
		}
		catch (BeanDefinitionStoreException ex) {
			// 注册失败时记录错误
			String msg = ex.getMessage();
			parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element);
			return null;
		}
	}
	return definition;
}

10.AbstractSingleBeanDefinitionParser.parseInternal方法

java 复制代码
/**
 * 构建 BeanDefinitionBuilder 实例,设置 Bean 的基础属性(父类、类名、作用域、
 * 延迟初始化等),并调用子类的 doParse 方法解析标签的自定义属性 / 子元素,
 * 最终生成 AbstractBeanDefinition。
 */
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
	// 创建BeanDefinitionBuilder(用于构建GenericBeanDefinition)
	BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
	// 设置父Bean名称(子类可通过getParentName重写,默认null)
	String parentName = getParentName(element);
	if (parentName != null) {
		builder.getRawBeanDefinition().setParentName(parentName);
	}
	// 设置Bean的类信息(优先类对象,其次类名)
	Class<?> beanClass = getBeanClass(element);
	if (beanClass != null) {
		builder.getRawBeanDefinition().setBeanClass(beanClass);
	}
	else {
		String beanClassName = getBeanClassName(element);
		if (beanClassName != null) {
			builder.getRawBeanDefinition().setBeanClassName(beanClassName);
		}
	}
	// 设置BeanDefinition的资源来源(用于错误定位)
	builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
	// 设置作用域(嵌套Bean继承父Bean的作用域,顶级Bean默认singleton)
	BeanDefinition containingBd = parserContext.getContainingBeanDefinition();
	if (containingBd != null) {
		// Inner bean definition must receive same scope as containing bean.
		builder.setScope(containingBd.getScope());
	}
	// 设置延迟初始化(继承全局默认配置,默认false)
	if (parserContext.isDefaultLazyInit()) {
		// Default-lazy-init applies to custom bean definitions as well.
		builder.setLazyInit(true);
	}
	// 委托子类解析自定义属性/子元素(核心扩展点)
	doParse(element, parserContext, builder);
	// 生成并返回AbstractBeanDefinition(GenericBeanDefinition)
	return builder.getBeanDefinition();
}

11.写在最后

本篇主要讲解了自定义标签的解析原理,是怎么获取到标签解析类的,怎么完成标签解析,最终将生成的BeanDefinition注册到容器中的。

相关推荐
.鸣2 小时前
idea学习日记10: 字符串相关类的底层原理
java·学习
在未来等你2 小时前
Kafka面试精讲 Day 24:Spring Kafka开发实战
java·spring boot·面试·kafka·消息队列·spring kafka·@kafkalistener
龙茶清欢3 小时前
1、Lombok入门与环境配置:理解Lombok作用、配置IDE与构建工具
java·spring boot·spring cloud
龙茶清欢3 小时前
2、Nginx 与 Spring Cloud Gateway 详细对比:定位、场景与分工
java·运维·spring boot·nginx·spring cloud·gateway
Eoch773 小时前
HashMap夺命十连问,你能撑到第几轮?
java·后端
每天进步一点_JL3 小时前
🔥 一个 synchronized 背后,JVM 到底做了什么?
后端
云动雨颤3 小时前
程序出错瞎找?教你写“会说话”的错误日志,秒定位原因
java·运维·php
魔芋红茶3 小时前
RuoYi 学习笔记 3:二次开发
java·笔记·学习
杨杨杨大侠3 小时前
Atlas Mapper 教程系列 (8/10):性能优化与最佳实践
java·spring boot·spring·性能优化·架构·系统架构