Springの初期化とXML解析の実現


前言
Springは何ですか?これはアプリケーションのフレームワークであり、アプリケーションの開発に強力なサポートを提供します。これもbean容器であり、beanオブジェクトのライフサイクル全体を管理し、beanオブジェクトの様々な存在状態を維持します。例えば、beanオブジェクトの実用化、廃棄、beanの単一のインスタンスと複数のインスタンス状態などです。
SpringはJavaの発展史において無視できない存在として、彼が改めてJavaを定義したと言っても過言ではない。それは機能が強くて、日常の開発にとても便利です。表面は簡単なものほど、背後は複雑です。
この章からSpringのソースコードを一緒に分析します。どうやって実現するかを見てください。よく使われるのはIOC、Annotation、AOP、事務などの機能です。
1、Springの入り口
私達のプロジェクトにはweb.xmlが必要です。Springのモニターが定義されています。

<listener>
   <listener-class>
      org.springframework.web.context.ContextLoaderListener
   </listener-class>
</listener>
Conteext Loader Listener類を見にきました。Servlet Contact Listenerインターフェースが実現されているのが見えます。
context InitializedはSpring初期化の入口方法です。
Springにはもう一つの入り口があります。org.springframe ebork.web.servlet.DisprpatServletという親子の器の関係で、最終的には同じ方法org.springframe ework.com.support.AbstractAppration Cont.freshに呼び出されます。
2、初期化
Springの初期化の第一歩は、設定ファイルをロードし、設定項目を解析することです。
ok、私達はXml WebAppleicationConteext類のloadBenDefinitions方法に来ます。

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
  String[] configLocations = getConfigLocations();
  if (configLocations != null) {
    for (String configLocation : configLocations) {
      reader.loadBeanDefinitions(configLocation);
    }
  }
}
configLocationsは構成ファイルであることが分かります。筆者のプロジェクトでは、プロファイルは一つしかありません。名前はappication Contect.xmlです。次のステップはこのプロファイルをloadBenDefinitionsという方法で解析します。
3、解析XML配置
まず、設定ファイルを一つのResourceオブジェクトにパッケージし、Resourceオブジェクトの入力ストリームを取得し、InputSourceオブジェクトに変換し、最後にDoccumentオブジェクトに解析します。下のコードはメイン部分だけ残しています。

public int loadBeanDefinitions(String location, Set<Resource> actualResources) 
             throws BeanDefinitionStoreException {
    ResourceLoader resourceLoader = getResourceLoader();
    if (resourceLoader instanceof ResourcePatternResolver) {
      // Resource pattern matching available.
      try {
        //   location      -applicationContext.xml,  Resource  
        Resource[] resources=resourceLoader).getResources(location);
        //  resources          JDK InputSource  ,     Document
        InputStream inputStream = resources.getInputStream();
        InputSource inputSource = new InputSource(inputStream);
        Document doc = doLoadDocument(inputSource, resource);
      }
      catch (IOException ex) {
        throw new BeanDefinitionStoreException(
            "Could not resolve bean definition resource pattern [" + location + "]", ex);
      }
    }
  }

appication Contect.xmlプロファイルをDockmentオブジェクトに解析しました。Rootノードの情報は以下の通りです。

[  
  [#text:], 
  [context:component-scan: null], 
  [#text:], 
  [bean: null], 
  [#text:], 
  [bean: null], 
  [#text:], 
  [bean: null],
  [#text:], 
  [bean: null], 
  [#text:], 
  [#comment:               
    viewClass:JstlView  JSP        JSTL   
    prefix  suffix:            ,            hello,   
            jsp         “WEB-INF/jsp/hello.jsp”], 
  [#text:], 
  [bean: null], 
  [#text: ]
]
4、ビーン情報をロードする
前回はSpringがappication Contact.xmlという設定ファイルをDcumentオブジェクトに解析したことを見ました。次はキーステップです。まずソースを見てください

  //      Document      ,         
  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;
          //       。
          //          (import、alias、bean、beans)
          //           (context:component-scan)
          if (delegate.isDefaultNamespace(ele)) {
            parseDefaultElement(ele, delegate);
          }
          else {
            delegate.parseCustomElement(ele);
          }
        }
      }
    }
    else {
      delegate.parseCustomElement(root);
    }
  }
4.1 component-scanの解析
まず、カスタム解析方法delegate.parseCustomelement(ele)に位置付けます。
最終的にorg.springframe ext.com.annotationComponentScann DefinitionParse(Element elent,PaserContxt parserContext)を呼び出しましたが、これはどうやって呼びますか?話が面白いです。
まずSpringの中の配置ファイルを見に行きます。/META-INF/spring.handers
http://www.spring frame ewark.org/schema/context=org.springframe ewark.com ntenfig.conspace Handler
http://www.spring frame ework.org/schema/je=org.springframe ework.ejb.com fig.Jee Namespace Handler
http://www.sprigframe ewark.org/schema/lang=org.sprigframe ewark.scripting.co nfig.LangNamespace Handler
http://www.springframe ework.org/schema/task=org.springframe ewark.scheduling.com fig.Task Namespace Handler
http://www.springframe ework.org/schema/cache=org.springframe ewark.cache.com nfig.namespace Handler
この中には異なるラベルの処理類が配置されています。例えばcontextラベル処理類はConttext Namespace Handlerです。そして反射実例化という処理類を通して、そのinit()方法を呼び出します。init()の方法の中には処理類が登録されています。その中に私達が興味を持っているcomponent-scanがあります。

  public NamespaceHandler resolve(String namespaceUri) {
    //handlerMappings     loadAllProperties(),  Spring      
    Map<String, Object> handlerMappings = getHandlerMappings();
    Object handlerOrClassName = handlerMappings.get(namespaceUri);
    if (handlerOrClassName == null) {
      return null;
    }
    else if (handlerOrClassName instanceof NamespaceHandler) {
      return (NamespaceHandler) handlerOrClassName;
    }
    else {
      String className = (String) handlerOrClassName;
      try {
        // context:component-scan  
        //     className  org.springframework.context.config.ContextNamespaceHandler
        //    ,     ContextNamespaceHandler,    init  
        Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
        NamespaceHandler namespaceHandler = BeanUtils.instantiateClass(handlerClass);
        namespaceHandler.init();
        handlerMappings.put(namespaceUri, namespaceHandler);
        return namespaceHandler;
      }
    }
  }
  public void init() {
    registerBeanDefinitionParser("annotation-config",
       new AnnotationConfigBeanDefinitionParser());
    registerBeanDefinitionParser("component-scan",
       new ComponentScanBeanDefinitionParser());
    //...  
  }
最終的にSpringは、component-scanというラベルを通じて、ComponentScanDefinitionPaser類を手に入れて、そのparse()メソッドを呼び出すことができます。

 public BeanDefinition parse(Element element, ParserContext parserContext) {
    //       ,        base-package="com.viewscenes.netsupervisor"
    String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
    basePackage = parserContext.getReaderContext().getEnvironment().
       resolvePlaceholders(basePackage);
    //          ,     
    String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
        ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

    /**
     * configureScanner      。
     * scanner.doScan     
     * registerComponents       registerComponents   
     * 
     * @return
     */
    ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
    Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
    registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

    return null;
  }
4.1.1 configreScanner配置スキャナ
この中のポイントはデフォルトのフィルタを登録したことです。use-default-filtersは、デフォルト値はtrueであり、この属性の値がfalseに設定されている場合、一部のコメントは追加されません。次のスキャン時にはBeanに登録できません。

protected void registerDefaultFilters() {
  //       use-default-filters,     false。     
  // Component、ManagedBean、Named         
  if (useDefaultFilters) { 
    this.includeFilters.add(new AnnotationTypeFilter(Component.class));
    ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
    try {
      this.includeFilters.add(new AnnotationTypeFilter(
      ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), 
                                     false));
      logger.debug("JSR-250 'javax.annotation.ManagedBean' 
                       found and supported for component scanning");
    }
    catch (ClassNotFoundException ex) {
      // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
    }   
    //...  
  }
}
4.1.2 doScanスキャン
ドシュanは三つのステップに分けられます。
  • findCandendidateComponentsは、パケット経路のすべてのclassファイルをスキャンし、Component注釈があるクラスをフィルタリングし、BeanDefinitionオブジェクトに変換し、LinkdHashSetに参加します。
  • は、前のステップで戻ってきたLinked HashSetをループし、setLazyInit、setSchopeなどの基本的な属性を設定します。
  • はBenDefinitionオブジェクトを登録し、Map容器にbeanNameとBenDefinitionをキャッシュし、ListにbeanNameを加入する。
  • 
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
        for (String basePackage : basePackages) {
          //findCandidateComponents    class  ,  Component  ,  BeanDefinition    。
          //      ,Component   @Component,  
          //@Controller、@Service、@Repository,             @Component。
         //         Component    。
          Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
          for (BeanDefinition candidate : candidates) {
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.
                                  resolveScopeMetadata(candidate);
            //    ,          
            candidate.setScope(scopeMetadata.getScopeName());
            candidate.setxxx(scopeMetadata.getxxxName());
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
            //registerBeanDefinition     BeanDefinition,       
            //this.beanDefinitionMap.put(beanName, beanDefinition);
            //this.beanDefinitionNames.add(beanName);
            registerBeanDefinition(definitionHolder, this.registry);
          }
        }
        return beanDefinitions;
      }
    
    
    最後に方法全体で返したのは、beanDefinitionオブジェクトのSet集合であり、二つのControllerを例にしている。
    
    [  
      Generic bean: class [com.viewscenes.netsupervisor.controller.IndexController]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\apache-tomcat-7.0.78\webapps\springmvc_dubbo_producer\WEB-INF\classes\com\viewscenes
    etsupervisor\controller\IndexController.class], Generic bean: class [com.viewscenes.netsupervisor.controller.UserController]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\apache-tomcat-7.0.78\webapps\springmvc_dubbo_producer\WEB-INF\classes\com\viewscenes
    etsupervisor\controller\UserController.class] ]
    4.1.3 annotationn-configへのサポート
    Springのプロファイルにはcontext:annotationn-configが配置されていますが、context:component-scanを配置したらconfigを再設定する必要はありません。component-scanを解析する時にすでにデフォルトでannotationn-figのサポートを追加しました。IOCの時は@Autowiredなどのコメントをサポートすることができませんでした。
    
    protected void registerComponents(XmlReaderContext readerContext, 
               Set<BeanDefinitionHolder> beanDefinitions, Element element) {
      boolean annotationConfig = true;
      if (element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)) {
        annotationConfig = Boolean.valueOf(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE));
      }
      if (annotationConfig) { //  annotation-config    
        Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<BeanDefinitionHolder>(4);
        if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
          RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
          beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
        }
        if (!registry.containsBeanDefinition(REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
          RootBeanDefinition def = new RootBeanDefinition(RequiredAnnotationBeanPostProcessor.class);
          beanDefs.add(registerPostProcessor(registry, def, REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
        }
        if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
          RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
          beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
        }
          ......  
      }
    }
    
    4.2 beanラベルの解析
    beanタグの解析は、デフォルトの処理方法です。
    beanタグのIDを取得し、beanNameをidとして付与し、別名を設定します。AbstractBeanDefinitionオブジェクトを新規作成し、反射によってbeanClassを設定し、property属性名と値を解析します。
    
    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
        //  bean_id 
        String id = ele.getAttribute(ID_ATTRIBUTE);
        String beanName = id;
        AbstractBeanDefinition beanDefinition = 
                   parseBeanDefinitionElement(ele, beanName, containingBean);
        String[] aliasesArray = StringUtils.toStringArray(aliases);
        //         beanName、class         BeanDefinition  
        return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
    }
    public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, 
                      String beanName, BeanDefinition containingBean) {
        String className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
        try {
          //  className    setBeanClass setBeanClassName
          AbstractBeanDefinition bd = createBeanDefinition(className, parent);
          //       setScope、setLazyInit、setAutowireMode...
          parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
          //  property   <bean><property name="id" value="1001"></property></bean>
          parsePropertyElements(ele, bd);
          return bd;
        }
        return null;
    }
    BeanDefinitionオブジェクトを登録して、component-scanスキャンのbeanと登録します。オブジェクトを容器に充填します。
    XML構成のビームであろうと、component-scanスキャンにより登録されたBeanであろうと、それらは最終的には同一のルートで、BeanDefinitionオブジェクトに変換されます。このBeanオブジェクトの属性と方法を記録しています。最後に容器に登録して、インスタンス化とIOCを待っています。
    以上が本文の全部です。皆さんの勉強に役に立つように、私たちを応援してください。