IOC流程解析-BeanFactory的创建

IOC流程解析-BeanFactory的创建,第1张

扑街前言:本次内容的重点是BeanFactory 的创建、BeanDefinition 的构建以及配置文件的解析、还有Schema 机制分析(这里要结合dubbo 的一点点内容,对于dubbo 有一定了解的小伙伴可以看下,没有的话建议跳过,后面看dubbo 的时候在回来看这个内容)。

Spring源码下载

在源码分析之前,有一个前提就是你能看到源码,你得本地有源码。那么我们先说下怎么下载spring 的源码,这里和其余框架不同,目前spring 的源码下载后的项目依赖管理不是maven 而是Gradle。这里关于Gradle 的下载教程我就不细说了,网上有很多可以自己去找找,我这里重点要说的是通过GitHub 将spring 的源码下载下来之后,遇到的一些常见问题。对了这里还要下载git啊。

  1. 源码下载下来后,需要在bin目录下,执行 gradlew.bat(建议命令行中执行)。
  2. 需要在源码根目录下右击出Git Bash Here,点击后出现命令窗口(这个git下载了才会有)后用命令配置自己的GitHub 用户名、邮箱、密码等信息,具体命令我放在最后吧。这样能解决”process ‘command’ ‘git’ finishend with non-zero exit value“这样的报错。
  3. 还要注意Gradle 的版本不好是最新版本,我这边的版本是5.6.4版本,还有spring 中的gradle.properties 的文件,需要主要其中的版本version 信息需要保持一致。
  4. 最后一个就是如果使用idea 遇到了编码问题的话,可以在Help => Edit Custom VM Options,点击后再文件中添加“-Dfile.encoding=UTF-8”。

这样将代码导入基本上就没有什么问题,需要注意的是spring 目前要需要jdk 是jdk11 以上版本。注释版源码的话可以访问我的CSDN资源下载:源码


BeanFactory的创建

源码下载用idea加载后,我们可以先创建一个测试类,这个可以方便于我们后面debug。

public static void main(String[] args) {
    ClassPathXmlApplicationContext applicationContext =
        new ClassPathXmlApplicationContext("classpath:applicationContext-cyclic.xml");

    TestService1 testService1 = (TestService1) applicationContext.getBean("testService1");
    TestService2 testService2 = (TestService2) applicationContext.getBean("testService2");
    testService1.aTest();
    testService2.aTest();
}

那么现在我们就可以启动后直接跟进ClassPathXmlApplicationContext 的构建里面,这个可以跟到的第一个代码点。这里只需要继续跟进核心方法。

public ClassPathXmlApplicationContext(
    String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
    throws BeansException {

    // 1.如果已经有 ApplicationContext 并需要配置成父子关系,那么调用这个构造方法
    super(parent);
    // 2.根据提供的路径,处理成配置文件数组(以分号、逗号、空格、tab、换行符分割)
    setConfigLocations(configLocations);

    // refresh值默认为true
    if (refresh) {
        // *核心方法
        refresh();
    }
}

下面这个方法内容过多,可以直接根据文章目录点击到prepareRefresh方法,下面这段代码后面会一点一点解析

public void refresh() throws BeansException, IllegalStateException {

  // synchronized块锁(monitorenter --monitorexit)
  // 不然 refresh() 还没结束,又来个启动或销毁容器的 *** 作
  //	 startupShutdownMonitor就是个空对象,锁
  synchronized (this.startupShutdownMonitor) {


    // Prepare this context for refreshing.
    /*
			   【1.准备刷新】
			      (1) 设置容器的启动时间
			      (2) 设置活跃状态为true
			      (3) 设置关闭状态为false
			      (4) 获取Environment对象,校验配置文件
			      (5) 准备监听器和事件的集合对象,默认为空的set集合
			 */
    prepareRefresh();

    // Tell the subclass to refresh the internal bean factory.
    /*
			   【2. 初始化 新BeanFactory】重点!
			      (1)如果存在旧 BeanFactory,则销毁
			      (2)创建新的 BeanFactory(DefaluListbaleBeanFactory)
			      (3)解析xml/加载 Bean 定义、注册 Bean定义到beanFactory(不初始化)
			      (4)返回新的 BeanFactory(DefaluListbaleBeanFactory)
			 */
    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

    // Prepare the bean factory for use in this context.
    // 【3. bean工厂前置 *** 作】为BeanFactory配置容器特性
    // 例如类加载器、表达式解析器、注册默认环境bean、后置管理器BeanPostProcessor
    prepareBeanFactory(beanFactory);

    try {
      // Allows post-processing of the bean factory in context subclasses.
      // 【4. bean工厂后置 *** 作】此处为空方法,如果子类需要,自己去实现
      postProcessBeanFactory(beanFactory);

      // Invoke factory processors registered as beans in the context.
      //【5、调用bean工厂后置处理器】,开始调用我们自己实现的接口
      // 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 回调方法
      invokeBeanFactoryPostProcessors(beanFactory);

      // Register bean processors that intercept bean creation.
      //【6. 注册bean后置处理器】只是注册,但是还不会调用
      //逻辑:找出所有实现BeanPostProcessor接口的类,分类、排序、注册
      registerBeanPostProcessors(beanFactory);

      // Initialize message source for this context.
      //【7、初始化消息源】国际化问题i18n
      initMessageSource(); // ===> 就是往factory加了个single bean

      // Initialize event multicaster for this context.
      //8、【初始化事件广播器】初始化自定义的事件监听多路广播器
      // 如果需要发布事件,就调它的multicastEvent方法
      // 把事件广播给listeners,其实就是起一个线程来处理,把Event扔给listener处理
      // (可以通过 SimpleApplicationEventMulticaster的代码来验证)
      initApplicationEventMulticaster(); // ===> 同样,加了个bean

      // Initialize other special beans in specific context subclasses.
      // 9、【刷新:拓展方法】这是个protected空方法,交给具体的子类来实现
      //  可以在这里初始化一些特殊的 Bean
      onRefresh();

      // Check for listener beans and register them.
      //10、【注册监听器】,监听器需要实现 ApplicationListener 接口
      // 也就是扫描这些实现了接口的类,给他放进广播器的列表中
      // 其实就是个观察者模式,广播器接到事件的调用时,去循环listeners列表,
      // 挨个调它们的onApplicationEvent方法,把event扔给它们。
      registerListeners();  // ===> 观察者模式

      // Instantiate all remaining (non-lazy-init) singletons.
      //11、 【实例化所有剩余的(非惰性初始化)单例】
      // (1)初始化所有的 singleton beans,反射生成对象/填充
      // (2)调用Bean的前置处理器和后置处理器
      finishBeanFactoryInitialization(beanFactory);

      // Last step: publish corresponding event.
      // 12、【结束refresh *** 作】
      // 发布事件与清除上下文环境
      finishRefresh();
    }

    catch (BeansException ex) {
      if (logger.isWarnEnabled()) {
        logger.warn("Exception encountered during context initialization - " +
                    "cancelling refresh attempt: " + ex);
      }

      // Destroy already created singletons to avoid dangling resources.
      destroyBeans();

      // Reset 'active' flag.
      cancelRefresh(ex);

      // Propagate exception to caller.
      throw ex;
    }

    finally {
      // Reset common introspection caches in Spring's core, since we
      // might not ever need metadata for singleton beans anymore...
      resetCommonCaches();
    }
  }
}

进入核心方法,这里我们就需要一个一个方法看了,不过这次我们只看到BeanFactory 的创建和配置文件的解析。

prepareRefresh方法

首选我们可以看到的就是prepareRefresh 方法,这个方法其主要作用就是如下5点:

  1. 设置容器的启动时间
  2. 设置活跃状态为true
  3. 设置关闭状态为false
  4. 获取Environment对象,校验配置文件
  5. 准备监听器和事件的集合对象,默认为空的set集合

其实主要就是做一些准备工作,具体代码可以自己去翻一下,我这里就不看了。

obtainFreshBeanFactory-构建BeanFactory的方法

这方法是我们本次的重点,其主要做用也就是如下4点,这里我们需要一点点看。

/*
 【2. 初始化 新BeanFactory】重点!
	(1)如果存在旧 BeanFactory,则销毁
	(2)创建新的 BeanFactory(DefaluListbaleBeanFactory)
	(3)解析xml/加载 Bean 定义、注册 Bean定义到beanFactory(不初始化)
	(4)返回新的 BeanFactory(DefaluListbaleBeanFactory)
*/
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

跟进该方法可以看到是主要调用refreshBeanFactory 和getBeanFactory 方法,这里的重点就是前者方法,后者只是将前者设置到的对象进行一个返回。

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
  // 1.关闭旧的 BeanFactory (如果有),创建新的 BeanFactory
  refreshBeanFactory();
  // 2.返回刚创建的 BeanFactory(ConfigurableListableBeanFactory)
  return getBeanFactory();
}
过度方法-refreshBeanFactory方法等等

跟进后我们来到的是AbstractRefreshableApplicationContext 类中,目前方法第一步就是先判断BeanFactory 是否存在,因为容器中只能存在一个BeanFactory 对象,存在则销毁,然后在创建一个全新的BeanFactory 对象,然后设置一些默认属性,然后就是继续方法调用,最后返回创建好的对象,这里的重点又是下一步方法loadBeanDefinitions。

@Override
protected final void refreshBeanFactory() throws BeansException {
  // 1.判断是否已经存在 BeanFactory,如果存在则先销毁、关闭该 BeanFactory
  if (hasBeanFactory()) {
    destroyBeans();
    closeBeanFactory();
  }
  try {
    // 2.创建一个新的BeanFactory
    DefaultListableBeanFactory beanFactory = createBeanFactory();
    // 设置标识,用于 BeanFactory 的序列化
    beanFactory.setSerializationId(getId());
    // 设置 BeanFactory 的两个配置属性:(1)是否允许 Bean 覆盖 (2)是否允许循环引用
    customizeBeanFactory(beanFactory);

    /*
				重点-->:加载 Bean 到 BeanFactory 中
				  1、通过BeanDefinitionReader解析xml为Document
				  2、将Document注册到BeanFactory 中(这时候只是bean的一些定义,还未初始化)
			 */
    // 3.加载 bean 定义
    loadBeanDefinitions(beanFactory);
    this.beanFactory = beanFactory;
  }
  catch (IOException ex) {
    throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
  }
}

因为我这里使用的xml 方式进行的配置,所以我进入的是AbstractXmlApplicationContext 类,如果是注解的话进入的就是AnnotationConfigWebApplicationContext 类。

这里基本工作就是获取一些资源解析器,用于后面的解析xml 配置信息,重点跟进的地方任然是loadBeanDefinitions 方法

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

  // Configure the bean definition reader with this context's
  // resource loading environment.
  // 2.使用此上下文的资源加载环境配置 XmlBeanDefinitionReader
  beanDefinitionReader.setEnvironment(this.getEnvironment());
  // 资源加载器
  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.
  // 校验,配置文件xsd和dtd头部
  initBeanDefinitionReader(beanDefinitionReader);

  // 3.加载 bean 定义 **===》
  loadBeanDefinitions(beanDefinitionReader);
}

依然在AbstractXmlApplicationContext 类中,这里调用进入的方法都是同一个,只有获取资源的路径的方式不一样而已,还是需要跟进loadBeanDefinitions 方法,不过这次是AbstractBeanDefinitionReader 类中。

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
  //下面的if分支一般会走第2个,无论走哪个,if里面的调的方法都是load
  // 分支1::获取Resource

  Resource[] configResources = getConfigResources();
  if (configResources != null) {
  	reader.loadBeanDefinitions(configResources);
  }
  
  // 分支2:获取资源的路径
  //前面解析出来的那些配置文件,classpath*:application.xml
  String[] configLocations = getConfigLocations();
  if (configLocations != null) {
  	//重要!解析xml的结构正是在这里开端!!!
  	reader.loadBeanDefinitions(configLocations);
  }
}

继续看就是还是跟进,这里的作用就是一个计数,这里会根据不同的配置文件重复调用loadBeanDefinitions。

这里下一步还是AbstractBeanDefinitionReader 类的loadBeanDefinitions 重载方法,不过这里任然是调用loadBeanDefinitions 方法。

最后的我们达到的是XmlBeanDefinitionReader 类的loadBeanDefinitions 方法,然后紧接着是调用其doLoadBeanDefinitions 方法。

@Override
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
   Assert.notNull(locations, "Location array must not be null");
   // 计数,来统计所有xml里一共有多少个bean
   int count = 0;
   for (String location : locations) {
      //继续进入loadBeanDefinitions,解析xml文件
      count += loadBeanDefinitions(location);
   }
   //最终返回的classpath*:application.xml中配置bean的个数
   return count;
}

在这里我们会将xml 使用inputSource和resource加载,并封装成Document 对象。然后传入registerBeanDefinitions 方法,这里还有一堆的异常分析捕捉。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
  throws BeanDefinitionStoreException {

  try {
    // 1.根据inputSource和resource加载XML文件,并封装成Document
    Document doc = doLoadDocument(inputSource, resource);

    // 2.根据返回的Document注册Bean信息(对配置文件的解析,核心逻辑) ====>
    int count = registerBeanDefinitions(doc, resource);
    if (logger.isDebugEnabled()) {
      logger.debug("Loaded " + count + " bean definitions from " + resource);
    }
    return count;
  }
  catch (BeanDefinitionStoreException ex) {
    throw ex;
  }
}

到了这里我们就已经快接近真正的解析了,我们可以直接看下面代码中的第3步BeanDefinitionDocumentReader 对象的registerBeanDefinitions 方法调用。

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
  // 1.获取documentReader,用于读取通过xml获得的document
  BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
  // 2.获取当前xml文件(document)解析前,已经注册的BeanDefinition数目
  int countBefore = getRegistry().getBeanDefinitionCount();
  // 3.解析并注册当前配置文件中的BeanDefinition =====》进入
  documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
  // 4.用当前注册的BeanDefinition数目减去之前注册的数目,返回该配置文件中注册BeanDefinition数目
  return getRegistry().getBeanDefinitionCount() - countBefore;
}

进入后就是doRegisterBeanDefinitions 方法的调用。这里我们依旧是看重点parseBeanDefinitions 方法。

protected void doRegisterBeanDefinitions(Element root) {
  /*
     我们看名字就知道,BeanDefinitionParserDelegate 必定是一个重要的类,它负责解析 Bean 定义,
     这里为什么要定义一个 parent? 看到后面就知道了,是递归问题,
     因为  内部是可以定义  的,
     所以这个方法的 root 其实不一定就是 xml 的根节点,也可以是嵌套在里面的  节点,从源码分析的角度,我们当做根节点就好了
   	*/
  BeanDefinitionParserDelegate parent = this.delegate;
  this.delegate = createDelegate(getReaderContext(), root, parent);

  // 1.校验root节点的命名空间是否为默认的命名空间(默认命名空间http://www.springframework.org/schema/beans)
  if (this.delegate.isDefaultNamespace(root)) {
    // 2.处理profile属性
    //
    String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
    if (StringUtils.hasText(profileSpec)) {
      //正常情况不会进入这里,具体代码就不展示了
      }
    }
  }

  /// 3.解析前处理, 留给子类实现
  preProcessXml(root);
  // 4.解析并注册bean定义, 核心解析方法,解析各种xml的标签,注册到BeanFactory!
  parseBeanDefinitions(root, this.delegate);   //====》
  // 5.解析后处理, 留给子类实现
  postProcessXml(root);

  this.delegate = parent;
}

总结一下上面的过度方法:就是将原本的配置信息,转换封装为方便解读的对象,然后准备初始化一系类的解析器,并对返回对象进行封装等等。这里也看出xml 配置和注解配置,也就是这里会有一定的区别。

解析配置信息-parseBeanDefinitions方法

继续到了这一步,我们就算到了真正解析xml配置文件的地方了,这里还会涉及到后面的Schema 机制分析。

这里一共是两种解析方式:parseDefaultElement 默认命名空间默认节点的处理,比如常规的bean 标签等等、parseCustomElement 自定义命名空间自定义节点的处理,比如dubbo 提供的dubbo:service 标签等或者我们自定的标签,还有就是spring 本身的一些标签比如aop 标签等,这里其实也就是Schema 机制的解析方式。

/**
	 * //解析beans下的xml节点,调用不同的方法来处理不同的节点
	 * @param root
	 * @param delegate
	 */
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
  // 遍历root的子节点列表
  // default namespace 涉及到的就四个标签 、、
  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)) {
          // 1.1 默认命名空间节点的处理,例如: 
          //分支1:代表解析标准元素 、、 这几个
          //标准节点
          parseDefaultElement(ele, delegate);
        }
        else {
          // 1.2 自定义命名空间节点的处理,例如:
          //分支2:代表解析 、 、
          //特殊节点
          delegate.parseCustomElement(ele);
        }
      }
    }
  }
  else {
    // 2.自定义命名空间的处理
    delegate.parseCustomElement(root);
  }
}
默认命名空间的解析方式-parseDefaultElement

我们就先看parseDefaultElement 的解析方式,首选是对各种默认标签进行分类处理,我们只看bean 标签的处理方式就好。

/**
	 * 标签解析
	 * @param ele
	 * @param delegate
	 */
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
  // 1.对import标签的处理
  if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
    // 
    importBeanDefinitionResource(ele);
  }
  // 2.对alias标签的处理
  else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
    // 
    processAliasRegistration(ele);
  }
  // 3.对bean标签的处理(最复杂最重要)
  else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
    // 处理  标签定义 ====》
    processBeanDefinition(ele, delegate);
  }
  // 4.对beans标签的处理
  else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
    // 如果碰到的是嵌套的  标签,需要递归
    doRegisterBeanDefinitions(ele);
  }
}
Bean标签的解析方法-processBeanDefinition

这里首先就是通过parseBeanDefinitionElement 方法解析标签中的所有内容,然后构建出BeanDefinition 这个对象,这个对象中存储的就是具体的配置信息,比如bean 的id、name、class 等属性,代码中虽然是BeanDefinitionHolder 但是它本身也就是对BeanDefinition 的一个封装而已。这里具体的解析标签的方法就详细看了,因为大部分跟市面上的解析方式都差不多,MyBatis 好像也就是一个套路。

这里的第二个重点就是registerBeanDefinition 注册方法,这个说直白一点就是为DefaultListableBeanFactory 对象,也就是往上面创建的BeanFactory 对象的beanDefinitionMap 集合添加对应的BeanDefinition 值,注意的是BeanDefinition 是一个ConcurrentHashMap 集合,并且这里是私有常量。

到这里Bean 的解析工作就算是结束了,同时也验证了上篇文章所有的是先创建了BeanFactory 对象,然后再解析构建的BeanDefinition 对象,最后又将BeanDefinition 对象存入了BeanFactory 对象中。

/**
	 * 两步:1、解析,封装到对象的属性上去,
	 *      2、注册,注册到factory里去(其实就是俩Map存起来)
	 */
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {

  // 1.bean节点具体的解析过程,属性,值等(bdHolder会包含一个Bean节点的所有属性,例如name、class、id)  ====》
  BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
  if (bdHolder != null) {
    // 2.若存在默认标签的子节点下再有自定义属性,需要再次对自定义标签再进行解析(基本不用,不做深入解析)
    bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
    try {
      // Register the final decorated instance.
      //3.注册Bean
      BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
    }
    catch (BeanDefinitionStoreException ex) {
      getReaderContext().error("Failed to register bean definition with name '" +
                               bdHolder.getBeanName() + "'", ele, ex);
    }
    // Send registration event.
    // 4.注册完成后,发送事件
    getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
  }
}


Schema 机制分析

结合上面的解析配置信息的内容,我们在这里简单的说一下Schema 机制,这里需要用到dubbo 的一点知识。

下面就是dubbo 的常用配置xml,结合刚刚上面的内容,spring 启动的时候是只能解析下面的配置的bean 标签,对于dubbo 标签是没有解析的,但是上面内容也提到自义定命名空间和标签,也是可以通过parseCustomElement 方法来进行解析的,而这段解析内容也可以算是schema 机制的重点。


<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    
    <dubbo:application name="demo-provider"  />
   

    <dubbo:registry address="zookeeper://127.0.0.1:2181" />

    

    
    <dubbo:protocol name="dubbo"  />

    
    <bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl"/>

    
    <dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService"/>

    <bean id="myDemoService" class="org.apache.dubbo.demo.provider.MyDemoServiceImpl" />

    <dubbo:service interface="org.apache.dubbo.demo.MyDemoService" ref="myDemoService"/>
beans>

自定义命名空间和标签

我们先要了解的是怎样才算是自定义命名空间,其实我们将上面的xml 配置和spring 常规xml 配置对比,就能发现beans 标签中多了一个xmlns:dubbo 的属性,而xsi:schemaLocation 属性中也多出了一段关于dubbo 的dubbo.xsd 配置。

这两个东西具体作用就是:dubbo.xsd 文件规定了dubbo 标签的语法;而http://dubbo.apache.org/schema/dubbo 对应着META-INF 目录下spring.handlers 文件中配置的解析器类的路径。dubbo.xsd 文件也是在这个目录下的spring.schemas 文件导向的。

自定义命名空间和标签的解析-parseCustomElement方法

既然知道了相关的语法和对象的解析器,那么我们就可以回到parseCustomElement 方法看看具体的解析内容,直接可以定位到BeanDefinitionParserDelegate 类的parseCustomElement 方法。

我们这里重点看两个地方,其一是:得到一个命名空间处理器,也就是resolve 方法、其二就是:开始解析,也就是parse 方法。

@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
  //解析节点的命名空间
  String namespaceUri = getNamespaceURI(ele);
  if (namespaceUri == null) {
    return null;
  }
  //解析命名空间,得到一个命名空间处理器
  //重点
  NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
  if (handler == null) {
    error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
    return null;
  }
  //开始解析
  //主线 重点
  return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

先看resolve 方法,这里我们其实要看的就是三个部分,初始化、缓存、返回,重点就是初始化。

这里可能还有人有疑惑,到底是怎么得到解析器的,其实就是spring 中的约定,约定好schema 就是通过spring.handlers 文件得到解析器,spring.schemas 文件规定语法,然后spring 项目中全部这个命名的文件内容,然后将信息存储下来。

既然我们上面已经得到了整个dubbo 的xml 配置文件,那么也就是得到了先关的beans 标签的值,这里就可以通过http://dubbo.apache.org/schema/dubbo 获取到相关的解析器,也就是DubboNamespaceHandler 对象,这个对象的顶层父类也就是NamespaceHandler 对象。

NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;

然后就是DubboNamespaceHandler 对象的init 初始化方法。这里的初始化写法是固定的,又spring 提供,这可以看到是将各个不同标签的解析对象都创建,并封装成了DubboBeanDefinitionParser 对象,那么后面调用的话只是调用了DubboBeanDefinitionParser 对象的parse 方法,而不是对应的解析器的parse 方法,比如service 标签对应的ServiceBean 解析器。

@Override
public void init() {
  /**
    * 解析配置文件中 相关的配置,并向容器中注册bean信息
    */
  registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
  registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
  registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
  registerBeanDefinitionParser("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true));
  registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true));
  registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
  registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.class, true));
  registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
  registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
  registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
  registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
  registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
  registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
}

DubboBeanDefinitionParser 对象的parse 方法的调用,这里则是通过不同的标签来构建出真正的解析器,比如service 标签对应的ServiceBean 解析器。

而解析器的真正调用,也就是真正的标签解析,我们后面说。这里解析器逻辑只针对dubbo 啊,其余的不同框架都有一定的差异,但是大体流程就是这样的。


总结

本次的内容的重点就是BeanFactory 的创建流程、BeanDefinition 的构建和存储、缓存,配置信息的解析。结合之前的流程图,这也是完成对象实例化和初始化之前的准备工作,目前BeanFactory 的单例池是没有任何配置对象的,添加对象那是后面的事情。下一篇就是BeanFactoryPostProcessor 和BeanPostProcessor 的区别了。


附录Spring 源码分析系列文章
时间文章
2022-03-09Spring的基本概念和IOC流程的简述
2022-03-11IOC流程解析-BeanFactory的创建
2022-03-14IOC流程解析-BeanFactoyPostProcessor和BeanPostProcessor
2022-03-15
2022-03-17

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/723658.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-26
下一篇 2022-04-26

发表评论

登录后才能评论

评论列表(0条)

保存