BeanDefinitionParserDelegate.parseCustomElement
-
- 1.写在前面
- 2.准备工作
- [3.前置知识:XML 约束规范](#3.前置知识:XML 约束规范)
- 4.BeanDefinitionParserDelegate.parseCustomElement方法
- 5.DefaultNamespaceHandlerResolver.resolve方法
- 6.DefaultNamespaceHandlerResolver.getHandlerMappings方法
- 7.PropertiesLoaderUtils.loadAllProperties方法
- 8.ContextNamespaceHandler.init方法
- 9.ContextNamespaceHandler.parse方法
- 10.AbstractSingleBeanDefinitionParser.parseInternal方法
- 11.写在最后
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注册到容器中的。