IOCプロセス分析 - BeanFactoryの作成

Pojie への序文: このコンテンツの焦点は、BeanFactory の作成、BeanDefinition の構築、設定ファイルの分析、およびスキーマ メカニズムの分析です (ここでは、ある程度の理解を持つ友人である dubbo のコンテンツを少し組み合わせる必要があります)ダボを読むことができます。そうでない場合はスキップすることをお勧めします。後でダボを視聴するときにこのコンテンツを読みに戻ります)。

Spring ソースコードのダウンロード

ソースコードを解析する前に、ソースコードが見えるという前提があり、ソースコードをローカルに持っていなければなりません。それでは、まず Spring のソースコードをダウンロードする方法について説明しますが、これが他のフレームワークと異なり、現時点では Spring のソースコードをダウンロードした後のプロジェクトの依存関係管理は Maven ではなく Gradle で行われます。Gradle のダウンロード チュートリアルについてはここでは詳しく説明しません。インターネット上には自分で見つけることができるものがたくさんあります。ここで焦点を当てたいのは、GitHub 経由で Spring のソース コードをダウンロードした後に遭遇する一般的な問題です。ちなみに、ここでgitをダウンロードする必要があります。

  1. ソース コードをダウンロードした後、bin ディレクトリで gradlew.bat を実行する必要があります (コマンド ラインで実行することをお勧めします)。
  2. ソース コードのルート ディレクトリにある Git Bash Here を右クリックし、クリックするとコマンド ウィンドウが表示され (この git ダウンロードのみが利用可能になります)、コマンドを使用して自分の GitHub ユーザー名、電子メール、パスワードやその他の情報。具体的なコマンドは最後に記載します。これにより、「プロセス 'command' 'git' がゼロ以外の終了値で終了します」というエラーを解決できます。
  3. また、Gradle のバージョンが最新ではないことに注意してください。私の側のバージョンはバージョン 5.6.4 であり、gradle.properties ファイルは春のものです。バージョン情報は一貫している必要があります。
  4. 最後の方法は、idea の使用中にエンコードの問題が発生した場合、[ヘルプ] => [カスタム VM オプションの編集] をクリックした後、ファイルに「-Dfile.encoding=UTF-8」を追加できることです。

このようにコードのインポートは基本的に問題ありませんが、現時点では Spring では jdk が jdk11 以降である必要があることに注意してください。コメント バージョンのソース コードについては、CSDN リソースのダウンロード:ソース コードにアクセスしてください。


BeanFactoryの作成

ソース コードのダウンロードにアイデアがロードされたら、最初にテスト クラスを作成できます。これは、後でデバッグするのに便利です。

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. 環境オブジェクトを取得し、構成ファイルを確認します。
  5. リスナーとイベントのコレクション オブジェクトを準備します。デフォルトは空のセット コレクションです。

実際、重要なことはいくつかの準備作業を行うことです。特定のコードは自分で実行できるため、ここでは説明しません。

getFreshBeanFactory - 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 オブジェクトは 1 つしか存在できず、存在する場合は破棄されるためです。次に、新しい 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);
  }
}

To continue to look is to follow up. ここでの関数は count です。ここで、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;
}

ここでは、inputSource と resource を使用して XML をロードし、それを 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? 看到后面就知道了,是递归问题,
     因为 <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;
}

上記の移行方法を要約すると、元の構成情報を解釈しやすいオブジェクトに変換およびカプセル化し、その後、一連のパーサーの初期化の準備をし、返されたオブジェクトなどをカプセル化します。ここでも、xml 構成とアノテーション構成、つまりここに一定の違いがあることがわかります。

構成情報の解析 - parseBeanDefinitions メソッド

このステップを続けると、XML 構成ファイルが実際に解析される場所に到達したとしても、その後のスキーマ メカニズムの分析も含まれます。

ここには 2 つの解析方法があります。parseDefaultElement は、通常の Bean タグなど、デフォルトの名前空間内のデフォルト ノードを処理します。parseCustomElement は、dubbo によって提供される dubbo:service タグなど、カスタム名前空間内のカスタム ノードを処理します。私たち自身のタグ、および aop タグなどの Spring 自体のいくつかのタグは、実際にはスキーマ メカニズムの解析方法です。

/**
	 * //解析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);
  }
}
デフォルトの名前空間の解析メソッド - parseDefaultElement

まず、parseDefaultElement の解析メソッドを見てみましょう。最初の選択は、さまざまなデフォルト タグを分類することです。Bean タグの処理メソッドだけを確認する必要があります。

/**
	 * 标签解析
	 * @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タグの解析方法 - processBeanDefinition

ここで最初に行うことは、parseBeanDefinitionElement メソッドを通じてタグ内のすべてのコンテンツを解析し、次に Bean の ID、名前、クラス、その他の属性などの特定の構成情報を格納する BeanDefinition オブジェクトを構築することです。 BeanDefinitionHolder、それ自体は BeanDefinition の単なるパッケージです。ここでは、タグを解析する具体的な方法について詳しく説明します。タグの解析方法のほとんどは市販されている解析方法と似ており、MyBatis はルーチンであると思われるためです。

ここでの 2 番目のポイントは registerBeanDefinition 登録メソッドです。これは、対応する BeanDefinition 値を DefaultListableBeanFactory オブジェクト、つまり、上で作成した BeanFactory オブジェクトの beanDefinitionMap コレクションに追加するのが簡単です。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));
  }
}


スキーマメカニズムの分析

上記の構成情報の解析の内容と組み合わせて、ここではスキーマのメカニズムについて簡単に説明しますが、ここではダボについての知識が少し必要です。

以下は dubbo の共通構成 XML です。上記の内容と組み合わせると、Spring が開始すると、以下の構成の Bean タグのみを解析できます。dubbo タグの解析はありませんが、上記の内容ではカスタム名前空間についても言及されており、 tags は、parseCustomElement メソッドを通じて解析することもでき、この解析コンテンツはスキーマ メカニズムの焦点とみなすこともできます。

<?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>

カスタムの名前空間とタグ

最初に理解する必要があるのは、カスタム名前空間とみなされるものです。実際、上記の XML 構成を通常の Spring XML 構成と比較すると、beans タグに xmlns:dubbo という追加属性があることがわかります。 xsi:schemaLocation の属性もあります。 dubbo に関する追加の dubbo.xsd 設定があります。

これら 2 つの具体的な機能は次のとおりです: dubbo.xsd ファイルは dubbo タグの文法を指定し、http://dubbo.apache.org/schema/dubbo は spring.handlers ファイルで設定されたパーサー クラスに対応します。 META-INF ディレクトリ パス。dubbo.xsd ファイルも、このディレクトリ内の spring.schemas ファイルによって指示されます。

画像-20220421232354561

カスタム名前空間とタグの解析 - parseCustomElement メソッド

関連する文法とオブジェクト パーサーがわかったので、parseCustomElement メソッドに戻って特定の解析内容を確認し、BeanDefinitionParserDelegate クラスの parseCustomElement メソッドを直接見つけることができます。

ここでは 2 つの場所に焦点を当てます。1 つは名前空間プロセッサの取得 (resolve メソッド)、もう 1 つは解析の開始 (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 メソッドを見てみましょう。ここでは、実際には、初期化、キャッシュ、および戻りの 3 つの部分を確認する必要があります。焦点は初期化です。

ここでまだ疑問を持つ人もいるかもしれません. パーサーを取得する方法は、実際には Spring での合意です. 合意されたスキーマは、spring.handlers ファイルを通じてパーサーを取得することであり、spring.schemas ファイルは構文を指定し、その後、すべての名前を指定しますSpring プロジェクト ファイルの内容を取得し、その情報を保存します。

上記のダボ全体の XML 設定ファイルを取得したので、first-closed Bean タグの値を取得しました。ここで、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 オブジェクトの解析メソッドのみが呼び出されます。パーサー (サービス タグに対応する ServiceBean パーサーなど)。

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

DubboBeanDefinitionParser オブジェクトの parse メソッドの呼び出しでは、サービス タグに対応する ServiceBean パーサーなど、さまざまなタグを通じて実際のパーサーを構築します。

画像-20220421234633341

そして、パーサーの実際の呼び出し、つまり実際のタグ解析については、後で説明します。ここでのパーサー ロジックは dubbo 専用であり、残りのさまざまなフレームワークには特定の違いがありますが、一般的なプロセスは次のとおりです。


要約する

今回の内容は、BeanFactoryの作成処理、BeanDefinitionの構築と保存、キャッシュ、構成情報の分析を中心に説明します。前のフローチャートと合わせて、これはオブジェクトのインスタンス化と初期化を完了する前の準備作業でもありますが、現時点では BeanFactory のシングルトン プールには構成オブジェクトはなく、オブジェクトの追加は後のことです。次の記事は、BeanFactoryPostProcessor と BeanPostProcessor の違いです。


付録 Spring ソースコード分析シリーズの記事

時間 記事
2022-03-09 Spring と IOC プロセスの基本概念の簡単な説明
2022-03-11 IOCプロセス分析 - BeanFactoryの作成
2022-03-14 IOC プロセス分析 - BeanFactoyPostProcessor および BeanPostProcessor
2022-03-15
2022-03-17

おすすめ

転載: blog.csdn.net/qq_39339965/article/details/124369725