spring 源码分析(1)-xml文件解析

本文涉及的产品
云解析DNS-重点域名监控,免费拨测 20万次(价值200元)
简介:

我们在最开始接触spring的时候,看到不少书spring入门的例子如下

ApplicationContext atx = new ClassPathXmlApplicationContext("application.xml");
atx.getBean("benefitService");

上面这个例子第一行是表示如何初始化一个spring 容器,第二表示如何从一个已经初始化后的spring容器中按bean id得到这个bean, 绝大部分spring应用中,bean都是按业务模板和层次分别配置在不同的xml文件中, spring容器根据配置的xml文件名路径去分别解析这些xml 配置文件,生成相应的BeanDefinition 实例,一个bean对应一个BeanDefinition, 解析完成bean 的xml配置文件之后,spring容器就开始初始bean,大概的过程如下:
image.png
这篇文章主要分析第一个阶段,即xml配置文件 ---->BeanDefinition这个过程,首先根据IDE工具看一下ClassPathXmlApplicationContext 这个类的继承关系:
image.png
通过这个继续关系,发现ClassPathXmlApplicationContext也是间接实现了ResourceLoader这个接口, ResourceLoader的实现类主要用于根据给定的资源文件地址返回对应的Resource,在本例中,这个资源文件就是application.xml;
接着往下看

public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
    this(new String[] {configLocation}, true, null);
}
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, 
                     ApplicationContext parent)  throws BeansException {
    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
        refresh();
    }
}

代码到了含有三个参数的构造方法,主要有三个步骤

super(parent)

这个步骤主要是调父类的构造器初始化容器的parent对象,这示例中,parent这个参数为空,其次是初始化资源模式解析器resourcePatternResolver,是一个实现了ResourceLoader的类,源码如下 :

public AbstractApplicationContext(ApplicationContext parent) {
        this.parent = parent;
        this.resourcePatternResolver = getResourcePatternResolver();
}
protected ResourcePatternResolver getResourcePatternResolver() {
        return new PathMatchingResourcePatternResolver(this);
}
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
        Assert.notNull(resourceLoader, "ResourceLoader must not be null");
        this.resourceLoader = resourceLoader;
}

setConfigLocations(configLocations)

这行代码,主要就是初始化configLocations这个数组字段,源码如下:

private String[] configLocations;
public void setConfigLocations(String[] locations) {
    if (locations != null) {
        Assert.noNullElements(locations, "Config locations must not be null");
        this.configLocations = new String[locations.length];
        for (int i = 0; i < locations.length; i++) {
            this.configLocations[i] = resolvePath(locations[i]).trim();
        }
    }
    else {
        this.configLocations = null;
    }
}

其中resolvePath主要解析并填充资源路径中的一些系统占位符,
如开始符:${,
结束符: }
分割符: :

refresh()

前面两步基本上都是容器本身的设置初始化,这个步骤才是spring 容器解析,创建初始化bean的关键步骤,点时去,我们发现这个方法长,只分析xml解析的过程,其它的在这里不一 一细说,在refresh方法中,第二代码是这样的:

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

直接进入org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory()这个方法,看一下这个方法体:

@Override
protected final void refreshBeanFactory() throws BeansException {
    if (hasBeanFactory()) {
        destroyBeans();
        closeBeanFactory();
    }
    try {
        DefaultListableBeanFactory beanFactory = createBeanFactory();
        beanFactory.setSerializationId(getId());
        customizeBeanFactory(beanFactory);
        loadBeanDefinitions(beanFactory);
        synchronized (this.beanFactoryMonitor) {
            this.beanFactory = beanFactory;
        }
    } catch (IOException ex) {
        throw new ApplicationContextException("I/O error parsing bean definition source for " + 
             getDisplayName(), ex);
    }
}

这个方法的大意是这样的:

  1. 先判断有没有beanFactory, 如果已经存在beanFactory,就先销毁beanFactory中所有的bean,再关闭这个beanFactory
  2. 创建一个DefaultListableBeanFactory 类型实例的beanFactory;
  3. 设置beanFactory的id,其中id是根据类名生成的,具体代码是:

     obj.getClass().getName() + "@" + getIdentityHexString(obj)
  4. 定置化beanFactory, 主要是设置: Bean是否需求覆盖重写,是否允bean循环引用,参数自动发现和注释字段bean匹配解析,例如Autowired注解;
    5.解析xml文档,并生成BeanDefinition对象实例集合;

重点看第5个步骤,即loadBeanDefinitions(beanFactory)这行代码,根据继承关系,直接进入org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(DefaultListableBeanFactory)这个方法

@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
    // Create a new XmlBeanDefinitionReader for the given BeanFactory.
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

    // Configure the bean definition reader with this context's
        // resource loading environment.
    beanDefinitionReader.setResourceLoader(this);
    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

    // Allow a subclass to provide custom initialization of the reader,
    // then proceed with actually loading the bean definitions.
    initBeanDefinitionReader(beanDefinitionReader);
    loadBeanDefinitions(beanDefinitionReader);
}

这个方法的逻辑也很清晰

  1. 创建一个XmlBeanDefinitionReader 对象,这个类主要定义读取的Document,并注册BeanDefinition的功能;
  2. 设置beanDefinitionReader 对象的ResourceLoader,即ClassPathXmlApplicationContext类对象;
  3. 设置beanDefinitionReader 对象的解析对象:ResourceEntityResolver
  4. 初始化beanDefinitionReader 对象,在spring框架中,这个方法为空,是一个重写spring框架的扩展点;
protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {}
  1. 正式解析xml文件,并生成BeanDefinitions, 方法体如下:
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
        for (String configLocation : configLocations) {
            reader.loadBeanDefinitions(configLocation);
        }
    }
}

针对每个资源文件,重点看下面这两行代码:

Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);

第一行代码主是获取路径中以classpath:开头的xml文件,当然也有很多其它前缀开头的资源,org.springframework.util.ResourceUtils 这个类中有详细的说明
第二主要是生成加载BeanDefinitions

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    return loadBeanDefinitions(new EncodedResource(resource));
}

前面都是一些准备过程,接下来,到了真正执行loadBeanDefinitions
image.png
image.png

  1. getValidationModeForResource这个方法主要是获取xml文件的验证模式,主要有DTD和XSD两种验证模式,这两种验证模式,spring都支持,方法源码如下:
    image.png

DTD模式示例:
image.png
XSD模式示例:
image.png

  1. 加载xml,并得到转换成对应的Document实例
  2. 解析并注册xml文件定义相关的bean;
    重点看一下步骤3:

相关的方法源码如下:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    // 使用 DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader对象
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        // 记录统计前已经加载的BeanDefinition数量
    int countBefore = getRegistry().getBeanDefinitionCount();
         // 加载并注册bean
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        // 记录本次加载的 BeanDefinition数量
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

重点分析 documentReader.registerBeanDefinitions(doc, createReaderContext(resource))这行代

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;

        logger.debug("Loading bean definitions");
        Element root = doc.getDocumentElement();

        BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);

        preProcessXml(root);
        parseBeanDefinitions(root, delegate);
        postProcessXml(root);
}

registerBeanDefinitions这个方法的逻辑也比较清晰

  1. 赋值readerContext
  2. 获取xml对象加载包装后对应Document对象
  3. 创建解析器 BeanDefinitionParserDelegate 类型的对象实例:delegate
  4. 处理xml文件的前置处理,默认为空,这是一个扩展点;
  5. 解析xml文件,并注册到spring容器中
  6. 处理xml文件的后轩处理,默认为空,这也是一个扩展点;
    重点看一下parseBeanDefinitions这个方法,方法实现源码如下:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    if (delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                if (delegate.isDefaultNamespace(ele)) {
                                        //默认标签bean的解析
                                        //<bean id="benefitService" class="com.tmall.xxx">
                    parseDefaultElement(ele, delegate);
                } else {
                                       //自定义标签bean的解析
                                        <tx:annotation-driven />
                                       <dubbo:service  timeout="3000" interface="com.tmall.xxx" ref="xxxx"/>
                                       <context:component-scan base-package="com.tmall.xxx"/>
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }  else {
              //自定义标签bean的解析
              <context:component-scan base-package="com.tmall.xxx"/>
        delegate.parseCustomElement(root);
        }
}

这个方法主要是针对bean的xml配置文件中默认标签和自定义标签分别进行解析
默认标签的解析方法parseDefaultElement的方法体如下:

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {  // 对import标签的解析处理
        importBeanDefinitionResource(ele);
    }
    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { // 对alias标签的解析处理
        processAliasRegistration(ele);
    }
    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {//对bean标签的解析处理
        processBeanDefinition(ele, delegate);
      }
      else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {//对beanS标签的解析处理
        processBeanDefinition(ele, delegate);
       }
}

到这个方法,我们终于看到与我们平时写spring bean配置文件相关的代码,下面结bean标签的解析处理代码进行分析
bean标签的解析最终会到parseBeanDefinitionElement方法,这个方法的部分代码如下:
image.png
这个方法的主要工作内容包括:

  1. 提供元素中的id和name属性
  2. 进一步解析其他所有属性并统一封装到AbstractBeanDefinition类型的实例中
  3. 如果检测到bean没有指定的beanName,那么使用默认的规则为此Bean生成beanName
    4.将获取到的信息封装到BeanDefinitionHolder的实例中。

步骤2中对其它标签的解析过程部分源码如下:

public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
            BeanDefinition containingBean, AbstractBeanDefinition bd) {

    if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {  // scope属性
        // Spring 2.x "scope" attribute
        bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
        if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) { // singleton 属性
            error("Specify either 'scope' or 'singleton', not both", ele);
        }
    }
    else if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
        // Spring 1.x "singleton" attribute
        bd.setScope(TRUE_VALUE.equals(ele.getAttribute(SINGLETON_ATTRIBUTE)) ?
                BeanDefinition.SCOPE_SINGLETON : BeanDefinition.SCOPE_PROTOTYPE);
    }
    else if (containingBean != null) {
        // Take default from containing bean in case of an inner bean definition.
        bd.setScope(containingBean.getScope());
    }

    if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {   // abstract属性
        bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
    }

    String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);  // lazy-init属性
    if (DEFAULT_VALUE.equals(lazyInit)) {
        lazyInit = this.defaults.getLazyInit();
    }
    bd.setLazyInit(TRUE_VALUE.equals(lazyInit));

    String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);  //autowire属性
    bd.setAutowireMode(getAutowireMode(autowire));

    String dependencyCheck = ele.getAttribute(DEPENDENCY_CHECK_ATTRIBUTE);
    bd.setDependencyCheck(getDependencyCheck(dependencyCheck));

    if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {  // depends-on属性
        String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
        bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, 
                       BEAN_NAME_DELIMITERS));
    }

         // autowire-candidate 属性
    String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
    if ("".equals(autowireCandidate) || DEFAULT_VALUE.equals(autowireCandidate)) {
        String candidatePattern = this.defaults.getAutowireCandidates();
        if (candidatePattern != null) {
            String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
                bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
        }
    }
    else {
        bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
    }
        // primary 属性
    if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
        bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
    }
        // init-method属性
    if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
        String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
        if (!"".equals(initMethodName)) {
            bd.setInitMethodName(initMethodName);
        }
    }
    else {
        if (this.defaults.getInitMethod() != null) {
            bd.setInitMethodName(this.defaults.getInitMethod());
            bd.setEnforceInitMethod(false);
        }
    }
        // destroy-method属性
    if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
        String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
        if (!"".equals(destroyMethodName)) {
            bd.setDestroyMethodName(destroyMethodName);
        }
    }
    else {
        if (this.defaults.getDestroyMethod() != null) {
            bd.setDestroyMethodName(this.defaults.getDestroyMethod());
            bd.setEnforceDestroyMethod(false);
        }
    }
       // factory-method 属性
    if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
        bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
    }
       // factory-bean 属性
    if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
        bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
    }
    return bd;
}

// 解析购造函数constructor-arg标签元素
public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {
    NodeList nl = beanEle.getChildNodes();
    for (int i = 0; i < nl.getLength(); i++) {
        Node node = nl.item(i);
        if (isCandidateElement(node) && 
                      nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) {
            parseConstructorArgElement((Element) node, bd);
        }
    }
}
//解析property属性
public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
    NodeList nl = beanEle.getChildNodes();
    for (int i = 0; i < nl.getLength(); i++) {
        Node node = nl.item(i);
        if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {
            parsePropertyElement((Element) node, bd);
        }
    }
}

其它的标签解析过程请参考下面这个类org.springframework.beans.factory.xml.BeanDefinitionParserDelegate

上面解析分析了默认标签bean的源码解析过程,下面再来看一下自定标签的解析过程,方法位置:org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(Element, BeanDefinition)源码如下:

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
         //获取对应的命名空间
    String namespaceUri = getNamespaceURI(ele);
        // 根据命名空间得到对相应的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;
    }
        // 调用自定义的NamespaceHandler handler进行解析
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

由于spring自定义标签大家在平时用得比较少,由于时间关系和篇幅关系,本来就不对自定义标签进行详细分析,下一篇文章会结合spring源码对自定义标签的使用和解析原理进行详细的介绍分析

由于时间关系,文中有些地方没有写细致,讲得不够清楚,可能不少地方还会出现低级的错误,请大家指正。

目录
相关文章
|
9月前
|
存储 Java 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `&lt;appender&gt;` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `&lt;logger&gt;` 和 `&lt;root&gt;` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
2140 1
|
9月前
|
XML Java 数据库连接
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——基于 xml 的整合
本教程介绍了基于XML的MyBatis整合方式。首先在`application.yml`中配置XML路径,如`classpath:mapper/*.xml`,然后创建`UserMapper.xml`文件定义SQL映射,包括`resultMap`和查询语句。通过设置`namespace`关联Mapper接口,实现如`getUserByName`的方法。Controller层调用Service完成测试,访问`/getUserByName/{name}`即可返回用户信息。为简化Mapper扫描,推荐在Spring Boot启动类用`@MapperScan`注解指定包路径避免逐个添加`@Mapper`
460 0
|
4月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
8月前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
503 70
|
9月前
|
XML JavaScript Android开发
【Android】网络技术知识总结之WebView,HttpURLConnection,OKHttp,XML的pull解析方式
本文总结了Android中几种常用的网络技术,包括WebView、HttpURLConnection、OKHttp和XML的Pull解析方式。每种技术都有其独特的特点和适用场景。理解并熟练运用这些技术,可以帮助开发者构建高效、可靠的网络应用程序。通过示例代码和详细解释,本文为开发者提供了实用的参考和指导。
301 15
|
10月前
|
Java API 数据处理
深潜数据海洋:Java文件读写全面解析与实战指南
通过本文的详细解析与实战示例,您可以系统地掌握Java中各种文件读写操作,从基本的读写到高效的NIO操作,再到文件复制、移动和删除。希望这些内容能够帮助您在实际项目中处理文件数据,提高开发效率和代码质量。
245 4
|
10月前
|
传感器 监控 安全
智慧工地云平台的技术架构解析:微服务+Spring Cloud如何支撑海量数据?
慧工地解决方案依托AI、物联网和BIM技术,实现对施工现场的全方位、立体化管理。通过规范施工、减少安全隐患、节省人力、降低运营成本,提升工地管理的安全性、效率和精益度。该方案适用于大型建筑、基础设施、房地产开发等场景,具备微服务架构、大数据与AI分析、物联网设备联网、多端协同等创新点,推动建筑行业向数字化、智能化转型。未来将融合5G、区块链等技术,助力智慧城市建设。
489 1
|
9月前
|
存储 监控 数据可视化
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
227 0
|
9月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
850 29

推荐镜像

更多
  • DNS