当前位置: 首页 > news >正文

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

BeanDefinitionParserDelegate.parseCustomElement

    • 1.写在前面
    • 2.准备工作
    • 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类

/*** 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;}
}

启动类

/*** 启动类*/
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 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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://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

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 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 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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://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

/*** 根据自定义命名空间标签的命名空间 URI,找到对应的 NamespaceHandler* (命名空间处理器),并委托处理器解析标签内容,生成 BeanDefinition* (或完成其他配置逻辑)*/
public BeanDefinition parseCustomElement(Element ele) {// 调用重载方法return parseCustomElement(ele, null);
}/*** 方法重载*/
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {// 获取自定义标签的命名空间URIString 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;}// 委托处理器解析标签,返回生成的BeanDefinitionreturn handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

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

/*** 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方法

/*** 根据传入的命名空间 URI,从预加载的映射关系中找到对应的 NamespaceHandler * 实例(或其类名),完成实例化、初始化后返回。*/
public NamespaceHandler resolve(String namespaceUri) {// 获取命名空间URI与处理器的映射关系(从spring.handlers加载)Map<String, Object> handlerMappings = getHandlerMappings();// 根据URI从映射中获取处理器对象或类名Object handlerOrClassName = handlerMappings.get(namespaceUri);if (handlerOrClassName == null) {// 无对应处理器,返回nullreturn null;}else if (handlerOrClassName instanceof NamespaceHandler) {// 无对应处理器,返回nullreturn (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方法

/*** 懒加载并缓存 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.handlersProperties 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方法

/*** 从类路径下查找所有名称为 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的初始化。这里我们看下初始化具体做了哪些事儿。其他的标签解析步骤同样如此

/*** 注册 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类图
在这里插入图片描述

/*** 这里再次贴处理我们当前的标签* <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的IDString 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方法

/*** 构建 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注册到容器中的。

http://www.dtcms.com/a/420241.html

相关文章:

  • 【c++】红黑树的部分实现
  • cpp02:类和对象
  • 即墨城乡建设局网站金融网站建设报价方案
  • 公司网站建设的建议电商网站建设期末考试
  • 自己的做网站cms资源
  • 图像采集卡:连接镜头与机器的“视觉神经”,释放工业智能核心动力
  • 北京移动端网站多少钱wordpress 首页跳转
  • 大连网站平台研发创客贴做网站吗
  • 如何撤销网站备案注册公司材料怎么准备
  • 学生可做的网站主题微信公众上传wordpress
  • 延吉建设局网站wordpress门户加商城
  • 高并发系统的高可用架构
  • 怎么自己开一个网站当当网网站开发计划和预算
  • 网站建设可行性报告范文网站建设 投资合作
  • 惠城网站建设有哪些做财经类新闻的网站
  • 有自己的网站做淘宝联盟号做吗房地产网站开发文档
  • 连云港公司企业网站建设网站创建人
  • C++ 并发编程最佳实践详解
  • 大型网站怎样做优化PHP国内优秀vi设计案例
  • 【论文精读】Few-Shot Object Detection with Attention-RPN and Multi-Relation Detector
  • 山东建设和城乡建设厅注册中心网站驾校视频网站模板
  • 莱芜住房和城乡建设厅网站专业网站建设哪家好
  • 时间轴网站模板仁怀哪儿做网站
  • 装修公司网站asp源码郴州做网站seo
  • 会网站开发想找兼职免费ppt模板下载简约
  • 网站运营与推广电子商务网站建设策略
  • 沈阳网站建设德泰诺谁有qq网站开发资源群
  • 北京公司网站建设定广州英铭网站建设
  • 网站建设详细步骤建建建设网站公司电话
  • 公司网站搭建费用wordpress积分 充值