• [spring源码学习]二、IOC源码——配置文件读取


    一、环境准备

      对于学习源码来讲,拿到一大堆的代码,脑袋里肯定是嗡嗡的,所以从代码实例进行跟踪调试未尝不是一种好的办法,此处,我们准备了一个小例子:

    package com.zjl;
    
    public class Person {
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void sayHello(){
            System.out.println("hello "+this.name);
        }
    }

      bean的定义:

        <bean id="person" class="com.zjl.Person">
            <property name="name" value="zhangsan"></property>
        </bean>  

      从很久以前,spring的第一个例子惯性的我们都是用XmlBeanFactory来进行,测试代码如下:

            XmlBeanFactory xmlBeanFactory=new XmlBeanFactory(new ClassPathResource("bean.xml"));
            Person person=(Person)xmlBeanFactory.getBean("person");
            person.sayHello();

      不过,很可惜,这个类在后来的版本中被删除了,不过没关系,他的源码很简单:

    public class XmlBeanFactory extends DefaultListableBeanFactory {
    
        private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
    
    
        /**
         * Create a new XmlBeanFactory with the given resource,
         * which must be parsable using DOM.
         * @param resource XML resource to load bean definitions from
         * @throws BeansException in case of loading or parsing errors
         */
        public XmlBeanFactory(Resource resource) throws BeansException {
            this(resource, null);
        }
    
        /**
         * Create a new XmlBeanFactory with the given input stream,
         * which must be parsable using DOM.
         * @param resource XML resource to load bean definitions from
         * @param parentBeanFactory parent bean factory
         * @throws BeansException in case of loading or parsing errors
         */
        public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
            super(parentBeanFactory);
            this.reader.loadBeanDefinitions(resource);
        }
    
    }

      我们可以看到,只是他继承了DefaultListableBeanFactory ,并持有一个XmlBeanDefinitionReader的引用,然后调用了this.reader.loadBeanDefinitions(resource)方法。所以我们很容易根据这个源码将程序改造一下

            DefaultListableBeanFactory beanFacory=new DefaultListableBeanFactory();
            XmlBeanDefinitionReader reader=new XmlBeanDefinitionReader(beanFacory);
            reader.loadBeanDefinitions(new ClassPathResource("bean.xml"));
            Person person=(Person)beanFacory.getBean("person");
            person.sayHello();

     二、源码解析

      结合上一节的内容,在本章节,我们只是详细看下针对xml配置的bean的一个加载过程,并不会调用getBean方法具体生成bean的对象,入口程序我们锁定为reader.loadBeanDefinitions(new ClassPathResource("bean.xml"))

      1、对resource进行了再次包装,添加编码和字符集

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

      2、读取配置文件,将inputStream转化为sax的inputSource,并设置字符集

    try {
                InputStream inputStream = encodedResource.getResource().getInputStream();
                try {
                    InputSource inputSource = new InputSource(inputStream);
                    if (encodedResource.getEncoding() != null) {
                        inputSource.setEncoding(encodedResource.getEncoding());
                    }
             //入口程序
    return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); } }

      3、进入doLoadBeanDefinitions方法,将inputSource转化为Dom解析的Document

                Document doc = doLoadDocument(inputSource, resource);
                return registerBeanDefinitions(doc, resource);

      4、创建dom的读取方法DefaultBeanDefinitionDocumentReader的实例,,并获取bean的数量,使用registerBeanDefinitions加载bean,解析完成后,返回此次加载bean的数量,注:此处对resource再次做了封装,疑似注入一些监听函数,但是暂时不管他们

        public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
            BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
            int countBefore = getRegistry().getBeanDefinitionCount();
            documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
            return getRegistry().getBeanDefinitionCount() - countBefore;
        }

      5、进入DefaultBeanDefinitionDocumentReader的registerBeanDefinitions方法,获取到配置文件的根节点beans,针对根节点开始遍历

        public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
            this.readerContext = readerContext;
            logger.debug("Loading bean definitions");
            Element root = doc.getDocumentElement();
            doRegisterBeanDefinitions(root);
        }

      6、创建了一个delegate,并判断节点是否为NameSpaceURI-http://www.springframework.org/schema/beans

        public boolean isDefaultNamespace(Node node) {
            return isDefaultNamespace(getNamespaceURI(node));
        }

      7、如果是默认的,获取profile属性,此处资料上可以配置开发、生产或者测试环境,暂不深究

            if (this.delegate.isDefaultNamespace(root)) {
                String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
                if (StringUtils.hasText(profileSpec)) {
                    String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                            profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
                    if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                        if (logger.isInfoEnabled()) {
                            logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                                    "] not matching: " + getReaderContext().getResource());
                        }
                        return;
                    }
                }
            }

      8、继续解析,在真正解析前后,可以使用pre和post方法对xml进行处理,此处均为空,只看parseBeanDefinitions方法即可

            preProcessXml(root);
         //入口 parseBeanDefinitions(root,
    this.delegate); postProcessXml(root);

      9、此处再次判断,root是否为默认的namespace,如果是,获取所有子节点,进行遍历子节点,并解析。如果根节点或者子节点不是默认的namespace,将使用delegate.parseCustomElement进行解析,由此可以看出,spring为我们预留了一个自定义配置文件解析的入口(此部分作为下一个章节重点分析)

      此次我们只有默认的namespace的应用,所以进行解析他的Element的子节点,我们知道唯一一个子节点为“bean”

    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)) {
                  //入口 parseDefaultElement(ele, delegate); }
    else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }

    10、对子节点的解析分为4中情况,分别为import,alias,bean,beans,其中

      beans:之前处理过,再次解析子节点即可

      import:表示引入另外的配置文件资源进行解析,此处预留其他文件解析入口

      alias:支持此格式<alias name="doSpring" alias="do"/>,是对一个bean起别名

      bean:是我们目前解析的重点,进入下一步解析

        private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
            if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
                importBeanDefinitionResource(ele);
            }
            else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
                processAliasRegistration(ele);
            }
            else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
          //入口 processBeanDefinition(ele, delegate); }
    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } }

    11、根据节点,创建一个BeanDefinitionHolder,

        protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
         //入口 BeanDefinitionHolder bdHolder
    = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // Register the final decorated instance. BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } // Send registration event. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }

    12、我们具体看下他的源码,可以知道它是根据Element的id,name等两个属性进行生成,如果有id,id作为beanName,如果id不存在,name的第一个作为beanName,其他作为别名,此处不再贴出,然后根据name和元素,创建一个AbstractBeanDefinition 

    AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);

    13、 分别获得className、parent、descriptioString className = null;        if (ele.hasAttribute(CLASS_ATTRIBUTE)) {            className = ele.getAttribute(CLASS_ATTRIBUTE).trim();

            }
    
            try {
                String parent = null;
                if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
                    parent = ele.getAttribute(PARENT_ATTRIBUTE);
                }
           //14、初始化GenericBeanDefinition AbstractBeanDefinition bd
    = createBeanDefinition(className, parent);        //15、设置bean的其他属性,包括单例,抽象,延迟加载、autowire、dependency-check、depends-on、autowire-candidate、primary、init-method、destroy-method、factory-method、factory-bean等属性,
    此部分的特点是有一个默认值,
    但也通过系统默认设置 parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));        //解析子标签中的meta标签 parseMetaElements(ele, bd);
           //解析look-up标签 parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
           //解析replaced-method标签 parseReplacedMethodSubElements(ele, bd.getMethodOverrides());        //constructor-arg标签,为了便于构造函数注入 parseConstructorArgElements(ele, bd);
          //解析Property标签,根据value或者ref标签分别注入TypedStringValue或RuntimeBeanReference到PropertyValue中 parsePropertyElements(ele, bd);
           //解析qualifier parseQualifierElements(ele, bd); bd.setResource(
    this.readerContext.getResource()); bd.setSource(extractSource(ele)); return bd; }

    14、与13一起,根据parentName和className创建GenericBeanDefinition的实例,此处classLoader不为null时候,似乎客户设置复杂的数据格式,暂时跳过

        public static AbstractBeanDefinition createBeanDefinition(
                String parentName, String className, ClassLoader classLoader) throws ClassNotFoundException {
    
            GenericBeanDefinition bd = new GenericBeanDefinition();
            bd.setParentName(parentName);
            if (className != null) {
                if (classLoader != null) {
                    bd.setBeanClass(ClassUtils.forName(className, classLoader));
                }
                else {
                    bd.setBeanClassName(className);
                }
            }
            return bd;
        }

    15、如果有其他标签,仍按照此方法继续处理。到此部分,我们已经解析完了配置文件中的所有标签

    16、根据别名,beanDefinition beanName创建BeanDefinitionHolder

    String[] aliasesArray = StringUtils.toStringArray(aliases);
                return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);

    17、对holder进行进一步装饰,主要装饰[autowire="default", autowire-candidate="default", class="com.zjl.Person", id="person", lazy-init="default"]等

    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
            BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
            if (bdHolder != null) {
                bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
                try {
                    // Register the final decorated instance.将最终装饰后的BeanDefinition注入registry,
                    BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
                }
                catch (BeanDefinitionStoreException ex) {
                    getReaderContext().error("Failed to register bean definition with name '" +
                            bdHolder.getBeanName() + "'", ele, ex);
                }
                // Send registration event.
                getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
            }

    18、调用registerBeanDefinition,注入bean时,先做一个父类检测((AbstractBeanDefinition) beanDefinition).validate()

    主要检测是否在配置文件中设置了factoryMethodName,

        public void validate() throws BeanDefinitionValidationException {
            if (!getMethodOverrides().isEmpty() && getFactoryMethodName() != null) {
                throw new BeanDefinitionValidationException(
                        "Cannot combine static factory method with method overrides: " +
                        "the static factory method must create the instance");
            }
    
            if (hasBeanClass()) {
                prepareMethodOverrides();
            }
        }

    19、将beanDefinition放入map,beanName放入list,将alias也放到map

    this.beanDefinitionMap.put(beanName, beanDefinition);
                    this.beanDefinitionNames.add(beanName);
                    this.manualSingletonNames.remove(beanName);

    20、触发注入的事件,此处触发了第四部分注入的监听事件,由于创建时候使用的XmlBeanDefinitionReader的new XmlReaderContext(resource, this.problemReporter, this.eventListener,this.sourceExtractor, this, getNamespaceHandlerResolver());方法中的EmptyReaderEventListener方法均为空,所以不做任何事情,此处我们也知道了,可以在自定义reader的子类中注入新的监听类,可以得到其他通知

    // Send registration event.
                getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));

    三、总结

    1、到此为止,整个ioc的配置文件读取和解析过程以及完成,此处只有一个bean,如果有多个,会从根继续读取下一个子节点进行解析。,最终我们在DefaultListableBeanFactory里得到了bean的map,beanName的list,alias的map等,通过这些容器就可以得到配置文件中bean的内容,但如何在调用getBean的时候获得到需要的bean,需要我们在以后的学习中进一步阅读源码

    2、在源码中我们可以看到除了自己配置的一些简单信息外,spring还有大量的默认配置和全局配置,这些配置的不同,是spring的各种功能的入口和配置,需要我们进行关注

    3、学习过程中,还有很多暂时不明白的功能,只能先跳过

  • 相关阅读:
    android利用ContentResolver访问者获取手机联系人信息
    Retrofit 实现获取往里圆角图片,且传值到另一个页面
    Android简单计时器
    几个比较常用的jar包
    Android双击Home键返回桌面
    两层fragment嵌套时出现空白,(收藏别人的)
    自动轮播本地图片触摸暂停轮播
    Fragment中TabLayout加FragmentViewPager
    Android 删除已知路径的文件或文件夹
    树莓派开机后获取正确时间
  • 原文地址:https://www.cnblogs.com/jyyzzjl/p/5422375.html
Copyright © 2020-2023  润新知