IOC process analysis - creation of BeanFactory

Foreword to Pujie: The focus of this content is the creation of BeanFactory, the construction of BeanDefinition, the analysis of configuration files, and the analysis of Schema mechanism (here we need to combine a little content of dubbo, friends who have a certain understanding of dubbo can read it, If not, it is recommended to skip it, and I will come back to read this content when I watch dubbo later).

Spring source code download

Before source code analysis, there is a premise that you can see the source code, and you have to have the source code locally. So let's first talk about how to download the source code of spring. This is different from other frameworks. At present, the project dependency management after downloading the source code of spring is not Maven but Gradle. I won’t go into details about the Gradle download tutorial here. There are many on the Internet that you can find by yourself. What I want to focus on here is some common problems encountered after downloading the source code of spring through GitHub. By the way, I have to download git here.

  1. After the source code is downloaded, you need to execute gradlew.bat in the bin directory (it is recommended to execute it on the command line).
  2. You need to right-click Git Bash Here in the root directory of the source code. After clicking, a command window will appear (this git download will only be available), and then use commands to configure your own GitHub user name, email, password and other information. I will put the specific commands at the end. . This can solve the error "process 'command' 'git' finish with non-zero exit value".
  3. Also note that the version of Gradle is not the latest version. The version on my side is version 5.6.4, and the gradle.properties file in spring. The version information needs to be consistent.
  4. The last one is that if you encounter encoding problems when using idea, you can add "-Dfile.encoding=UTF-8" to the file after clicking Help => Edit Custom VM Options.

In this way, there is basically no problem in importing the code. It should be noted that spring currently requires jdk to be jdk11 or above. For the source code of the comment version, you can visit my CSDN resource download: source code


Creation of BeanFactory

After the source code download is loaded with idea, we can create a test class first, which is convenient for us to debug later.

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();
}

So now we can directly follow up the construction of ClassPathXmlApplicationContext after startup, which is the first code point that can be followed. Here you only need to continue to follow up the core method.

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

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

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

The following method has too much content, you can directly click on the prepareRefresh method according to the article directory, and the following code will be analyzed bit by bit later

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();
    }
  }
}

Entering the core method, here we need to look at each method, but this time we only see the creation of BeanFactory and the analysis of configuration files.

prepareRefresh method

The first thing we can see is the prepareRefresh method. The main functions of this method are the following 5 points:

  1. Set the start time of the container
  2. Set the active state to true
  3. Set closed state to false
  4. Get the Environment object and verify the configuration file
  5. Prepare the collection object of listeners and events, the default is an empty set collection

In fact, the main thing is to do some preparatory work. You can go through the specific code yourself, so I won’t read it here.

obtainFreshBeanFactory - method to build a BeanFactory

This method is our focus this time, and its main functions are the following 4 points, here we need to look at it a little bit.

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

Following up this method, we can see that the refreshBeanFactory and getBeanFactory methods are mainly called. The focus here is the former method, and the latter just returns the object to which the former is set.

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

Excessive methods - refreshBeanFactory method, etc.

After follow-up, we came to the AbstractRefreshableApplicationContext class. The first step of the current method is to judge whether the BeanFactory exists, because there can only be one BeanFactory object in the container, and if it exists, it will be destroyed, and then create a new BeanFactory object, and then set some The default attribute, and then continue the method call, and finally return the created object. The focus here is the next step method 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);
  }
}

Because of the xml configuration I use here, I enter the AbstractXmlApplicationContext class, and if it is an annotation, I enter the AnnotationConfigWebApplicationContext class.

The basic work here is to obtain some resource parsers for later parsing xml configuration information, and the focus of follow-up is still the loadBeanDefinitions method

@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);
}

Still in the AbstractXmlApplicationContext class, the method of calling here is the same, only the way to obtain the resource is different. It still needs to follow up the loadBeanDefinitions method, but this time it is in the AbstractBeanDefinitionReader class.

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);
  }
}

To continue to look is to follow up. The function here is a count. Here, loadBeanDefinitions will be called repeatedly according to different configuration files.

The next step here is the loadBeanDefinitions overload method of the AbstractBeanDefinitionReader class, but here is still to call the loadBeanDefinitions method.

The last thing we reach is the loadBeanDefinitions method of the XmlBeanDefinitionReader class, followed by a call to its doLoadBeanDefinitions method.

@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;
}

Here we will load the xml using inputSource and resource, and encapsulate it into a Document object. Then pass in the registerBeanDefinitions method, and there are a bunch of exception analysis captures here.

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

At this point we are close to the real analysis, we can directly look at the registerBeanDefinitions method call of the BeanDefinitionDocumentReader object in step 3 in the following code.

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

After entering, it is the call of the doRegisterBeanDefinitions method. Here we still focus on the parseBeanDefinitions method.

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

To sum up the transition method above: it is to convert and encapsulate the original configuration information into objects that are easy to interpret, and then prepare to initialize a series of parsers, and encapsulate the returned objects, etc. It can also be seen here that xml configuration and annotation configuration, that is, there will be a certain difference here.

Parse configuration information - parseBeanDefinitions method

Continuing to this step, even if we have reached the place where the xml configuration file is actually parsed, it will also involve the subsequent analysis of the Schema mechanism.

There are two parsing methods here: parseDefaultElement handles the default node in the default namespace, such as regular bean tags, etc., parseCustomElement handles the custom node in the custom namespace, such as the dubbo:service tag provided by dubbo, etc. or our own Tags, and some tags of spring itself, such as aop tags, etc., here is actually the parsing method of the Schema mechanism.

/**
	 * //解析beans下的xml节点,调用不同的方法来处理不同的节点
	 * @param root
	 * @param delegate
	 */
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    
    
  // 遍历root的子节点列表
  // default namespace 涉及到的就四个标签 <import />、<alias />、<bean /> 和 <beans />
  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 默认命名空间节点的处理,例如: <bean id="test" class="" />
          //分支1:代表解析标准元素 <import />、<alias />、<bean />、<beans /> 这几个
          //标准节点
          parseDefaultElement(ele, delegate);
        }
        else {
    
    
          // 1.2 自定义命名空间节点的处理,例如:<context:component-scan/>、<aop:aspectj-autoproxy/>
          //分支2:代表解析 <mvc />、<task />、<context />、<aop /> 、<component-scan />等
          //特殊节点
          delegate.parseCustomElement(ele);
        }
      }
    }
  }
  else {
    
    
    // 2.自定义命名空间的处理
    delegate.parseCustomElement(root);
  }
}
The parsing method of the default namespace - parseDefaultElement

Let's first look at the parsing method of parseDefaultElement. The first choice is to classify various default tags. We only need to look at the processing method of bean tags.

/**
	 * 标签解析
	 * @param ele
	 * @param delegate
	 */
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    
    
  // 1.对import标签的处理
  if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
    
    
    // <import resource="classpath:applicationContext-datasource.xml" />
    importBeanDefinitionResource(ele);
  }
  // 2.对alias标签的处理
  else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
    
    
    // <alias name="abc" alias="af"/>
    processAliasRegistration(ele);
  }
  // 3.对bean标签的处理(最复杂最重要)
  else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
    
    
    // 处理 <bean /> 标签定义 ====》
    processBeanDefinition(ele, delegate);
  }
  // 4.对beans标签的处理
  else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
    
    
    // 如果碰到的是嵌套的 <beans /> 标签,需要递归
    doRegisterBeanDefinitions(ele);
  }
}
Bean tag analysis method - processBeanDefinition

The first thing to do here is to parse all the content in the tag through the parseBeanDefinitionElement method, and then build the BeanDefinition object, which stores specific configuration information, such as the bean's id, name, class and other attributes. Although the code is BeanDefinitionHolder, it itself It is just a package of BeanDefinition. Here is a detailed look at the specific method of parsing tags, because most of them are similar to the parsing methods on the market, and MyBatis seems to be a routine.

The second point here is the registerBeanDefinition registration method. This is straightforward to add the corresponding BeanDefinition value to the DefaultListableBeanFactory object, that is, to the beanDefinitionMap collection of the BeanFactory object created above. Note that BeanDefinition is a ConcurrentHashMap collection, and here is private constant.

At this point, even if the Bean parsing work is over, it also verifies that all the previous articles created the BeanFactory object first, then parsed the constructed BeanDefinition object, and finally stored the BeanDefinition object into the BeanFactory object.

/**
	 * 两步: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 Mechanism Analysis

Combined with the content of parsing configuration information above, we briefly talk about the Schema mechanism here, and a little knowledge of dubbo is needed here.

The following is the common configuration xml of dubbo. Combined with the content just above, when spring starts, it can only parse the bean tags of the configuration below. There is no parsing for dubbo tags, but the above content also mentions custom namespaces and tags , can also be parsed through the parseCustomElement method, and this parsing content can also be regarded as the focus of the schema mechanism.

<?xml version="1.0" encoding="UTF-8"?>
<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">

    <!-- provider's application name, used for tracing dependency relationship -->
    <dubbo:application name="demo-provider"  />
   <!-- <dubbo:metadata-report address="zookeeper://192.168.200.129:2181"/>-->

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

    <!--<dubbo:provider export="true" scope="remote" />-->

    <!-- use dubbo protocol to export service on port 20880 -->
    <dubbo:protocol name="dubbo"  />

    <!-- service implementation, as same as regular local bean -->
    <bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl"/>

    <!-- declare the service interface to be exported -->
    <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>

Custom namespaces and tags

The first thing we need to understand is what is considered a custom namespace. In fact, we compare the above xml configuration with the regular spring xml configuration, and we can find that there is an additional attribute of xmlns:dubbo in the beans tag, and there is also an attribute of xsi:schemaLocation There is an extra dubbo.xsd configuration about dubbo.

The specific functions of these two things are: the dubbo.xsd file specifies the grammar of the dubbo tag; and http://dubbo.apache.org/schema/dubbo corresponds to the parser class configured in the spring.handlers file under the META-INF directory path of. The dubbo.xsd file is also directed by the spring.schemas file in this directory.

image-20220421232354561

Parsing of custom namespaces and tags - parseCustomElement method

Now that we know the relevant grammar and object parser, we can go back to the parseCustomElement method to see the specific parsing content, and we can directly locate the parseCustomElement method of the BeanDefinitionParserDelegate class.

Here we focus on two places, one is: get a namespace processor, that is, the resolve method, and the other is: start parsing, that is, the parse method.

@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));
}

Let's look at the resolve method first. Here we actually need to look at three parts, initialization, caching, and return. The focus is on initialization.

Some people may still have doubts here. How to get the parser is actually the agreement in spring. The agreed schema is to get the parser through the spring.handlers file, the spring.schemas file specifies the syntax, and then all the names in the spring project file content, and then store the information.

Now that we have obtained the xml configuration file of the entire dubbo above, we have obtained the value of the first-closed beans tag, and here we can obtain the relevant parser through http://dubbo.apache.org/schema/dubbo, That is, the DubboNamespaceHandler object, and the top-level parent class of this object is also the NamespaceHandler object.

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

Then there is the init initialization method of the DubboNamespaceHandler object. The initialization method here is fixed and provided by spring. It can be seen that the parsing objects of different tags are created and encapsulated into DubboBeanDefinitionParser objects. Then the subsequent calls only call the parse method of the DubboBeanDefinitionParser object instead of corresponding The parse method of the parser, such as the ServiceBean parser corresponding to the service tag.

@Override
public void init() {
    
    
  /**
    * 解析配置文件中<dubbo:xxx></dubbo:xxx> 相关的配置,并向容器中注册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());
}

The call of the parse method of the DubboBeanDefinitionParser object, here is to build a real parser through different tags, such as the ServiceBean parser corresponding to the service tag.

image-20220421234633341

And the real call of the parser, that is, the real tag parsing, we will talk about later. The parser logic here is only for dubbo, and the rest of the different frameworks have certain differences, but the general process is like this.


Summarize

The content of this time focuses on the creation process of BeanFactory, the construction and storage of BeanDefinition, caching, and the analysis of configuration information. Combined with the previous flowchart, this is also the preparatory work before completing object instantiation and initialization. Currently, the singleton pool of BeanFactory does not have any configuration objects, and adding objects is a later thing. The next article is the difference between BeanFactoryPostProcessor and BeanPostProcessor.


Appendix Spring Source Code Analysis Series Articles

time article
2022-03-09 A brief introduction to the basic concepts of Spring and the IOC process
2022-03-11 IOC process analysis - creation of BeanFactory
2022-03-14 IOC process analysis - BeanFactoyPostProcessor and BeanPostProcessor
2022-03-15
2022-03-17

Guess you like

Origin blog.csdn.net/qq_39339965/article/details/124369725