zoukankan      html  css  js  c++  java
  • 通俗理解spring源码(五)—— 解析及注册BeanDefinitions

    通俗理解spring源码(五)—— 解析及注册BeanDefinitions

      上节讲到了如何获取document,当把文件转换为document后,接下来的提取及注册bean就是我们的重头戏。

        protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
                throws BeanDefinitionStoreException {
    
            try {
                //从资源文件转换为document对象
                Document doc = doLoadDocument(inputSource, resource);
                //解析document,并注册beanDefiniton到工厂中
                int count = registerBeanDefinitions(doc, resource);
                if (logger.isDebugEnabled()) {
                    logger.debug("Loaded " + count + " bean definitions from " + resource);
                }
                return count;
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (SAXParseException ex) {
                throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                        "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
            }
            catch (SAXException ex) {
                throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                        "XML document from " + resource + " is invalid", ex);
            }
            catch (ParserConfigurationException ex) {
                throw new BeanDefinitionStoreException(resource.getDescription(),
                        "Parser configuration exception parsing XML from " + resource, ex);
            }
            catch (IOException ex) {
                throw new BeanDefinitionStoreException(resource.getDescription(),
                        "IOException parsing XML document from " + resource, ex);
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(resource.getDescription(),
                        "Unexpected exception parsing XML document from " + resource, ex);
            }
        }

      在xmlBeanDefinitionReader的doLoadBeanDefinitions方法中,将document对象交给registerBeanDefinitions方法,返回本次加载的BeanDefinition个数。

        public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
            //创建默认的documentReader,即DefaultBeanDefinitionDocumentReader,用来解析document
            BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
            //获取注册中心,记录统计前的BeanDefinition加载个数
            int countBefore = getRegistry().getBeanDefinitionCount();
            //加载及注册BeanDefinition
            documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
            //记录本次加载的BeanDefinition个数
            return getRegistry().getBeanDefinitionCount() - countBefore;
        }

      这里首先会createReaderContext(resource),代码很简单。

        public XmlReaderContext createReaderContext(Resource resource) {
            return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
                    this.sourceExtractor, this, getNamespaceHandlerResolver());
        }

      这里重点是将this对象,也就是当前的xmlBeanDefinitionReader对象放进去了,所以XmlReaderContext相当于一个上下文,方便数据的传递,类似于ServletContext,SecurityContext等。

      然后将document和上下文对象交给documentReader.registerBeanDefinitions方法。

        public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
            this.readerContext = readerContext;
            doRegisterBeanDefinitions(doc.getDocumentElement());
        }

      此处引用了之前的上下文,然后调用doRegisterBeanDefinitions,在spring中,很多以"do"开头的方法名就是真正干活的方法。

      这里获取了document的根元素,进入documentReader.doRegisterBeanDefinitions方法

        protected void doRegisterBeanDefinitions(Element root) {
            // Any nested <beans> elements will cause recursion in this method. In
            // order to propagate and preserve <beans> default-* attributes correctly,
            // keep track of the current (parent) delegate, which may be null. Create
            // the new (child) delegate with a reference to the parent for fallback purposes,
            // then ultimately reset this.delegate back to its original (parent) reference.
            // this behavior emulates a stack of delegates without actually necessitating one.
            BeanDefinitionParserDelegate parent = this.delegate;
            //委托给delegate解析
            this.delegate = createDelegate(getReaderContext(), root, parent);
    
            //判断当前Beans节点是否是默认命名空间
            if (this.delegate.isDefaultNamespace(root)) {
                //获取beans节点的profile属性
                String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
                if (StringUtils.hasText(profileSpec)) {
                    //可以使用逗号或分号将当前beans标签指定为多个profile类型
                    String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                            profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
                    // We cannot use Profiles.of(...) since profile expressions are not supported
                    // in XML config. See SPR-12458 for details.
                    //判断当前beans标签的profile是否被激活
                    if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                                    "] not matching: " + getReaderContext().getResource());
                        }
                        return;
                    }
                }
            }
            //解析前处理,留给子类实现
            preProcessXml(root);
            //真正的解析过程
            parseBeanDefinitions(root, this.delegate);
            //解析后处理,留给子类实现
            postProcessXml(root);
    
            this.delegate = parent;

       可以先根据我的注释看看这个方法到底是在干啥,此处documentReader对象引用了一个BeanDefinitionParserDelegate,又将解析过程委托给了delegate 处理,在parseBeanDefinitions(root, this.delegate)方法中实现。

      方法一开头,便有一大段英文注释,大致意思就是说,任何内嵌的beans标签,将会导致该方法的递归调用,为了正确的传播和保留<beans>标签的default属性,追踪当前delegate(即父delegete,可能为null),每次都会创建一个新的delegate(即子delegate),引用父delegate,而这个子delegate下次又会作为父delegate。

      脑瓜子是不是有点嗡嗡的???但是大概也能明白这里就是为了处理beans标签的default属性的。

      因为我们在配置xml文件的时候,是可以在根beans标签中嵌套beans标签的(虽然这样的写法很少,一般是写在另外一个xml文件中,然后通过import标签导入,但实际上效果是一样的),类似这样:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="byType">
        <beans profile="test" default-autowire="byType">
            <bean id="user" class="cn.zxh.po.User" >
                <property name="name" value="张三"/>
            </bean>
        </beans>
        <beans profile="dev" default-autowire="constructor">
            <bean id="user" class="cn.zxh.po.User">
                <property name="name" value="李四"/>
            </bean>
        </beans>
    </beans>

      理论上没有栈溢出的情况下beans内部应该是可以无限嵌套beans的(不一定正确,还没有试过),后面会讲到每次解析到beans标签都会进入到该方法中,所以该方法可能会递归调用,每次都会创建delegate,对应一个beans标签,根据父Delegate来决定当前Delegate的默认属性。

    1、创建Delegate

            BeanDefinitionParserDelegate parent = this.delegate;
            this.delegate = createDelegate(getReaderContext(), root, parent);

      createDelegate点进入看看。

        protected BeanDefinitionParserDelegate createDelegate(
                XmlReaderContext readerContext, Element root, @Nullable BeanDefinitionParserDelegate parentDelegate) {
    
            BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
            //根据父delegate的默认属性,初始化当前beans的默认属性
            delegate.initDefaults(root, parentDelegate);
            return delegate;
        }

       首先会实例化一个BeanDefinitionParserDelegate对象,该对象引用了之前的上下文readerContext,并且还引用了一个DocumentDefaultsDefinition。

        private final DocumentDefaultsDefinition defaults = new DocumentDefaultsDefinition();
        public BeanDefinitionParserDelegate(XmlReaderContext readerContext) {
            Assert.notNull(readerContext, "XmlReaderContext must not be null");
            this.readerContext = readerContext;
        }

       DocumentDefaultsDefinition保存了根节点的默认配置属性值,比如说default-lazyInit,default-autowire,default-initMethod,default-destroyMethod等,这几种属性应该都用过吧,如果该beans标签下的bean没有配置这些属性,就会使用beans标签的默认配置。

      所以在这里,delegate引用了一个DocumentDefaultsDefinition,将来在解析各个bean标签时会起作用,然后调用initDefaults(root, parentDelegate),就是根据父delegate,初始化它自身的DefaultsDefinition。

      在initDefaults方法中大概就是采用子配置优先的原则给DocumentDefaultsDefinition属性赋值的,具体就不带大家细看了,不然很容易从入门到放弃,反正经过几层方法的调用,最终进入到这个方法中,这里仅仅贴一下代码。

        protected void populateDefaults(DocumentDefaultsDefinition defaults, @Nullable DocumentDefaultsDefinition parentDefaults, Element root) {
            String lazyInit = root.getAttribute(DEFAULT_LAZY_INIT_ATTRIBUTE);
            if (isDefaultValue(lazyInit)) {
                // Potentially inherited from outer <beans> sections, otherwise falling back to false.
                lazyInit = (parentDefaults != null ? parentDefaults.getLazyInit() : FALSE_VALUE);
            }
            defaults.setLazyInit(lazyInit);
    
            String merge = root.getAttribute(DEFAULT_MERGE_ATTRIBUTE);
            if (isDefaultValue(merge)) {
                // Potentially inherited from outer <beans> sections, otherwise falling back to false.
                merge = (parentDefaults != null ? parentDefaults.getMerge() : FALSE_VALUE);
            }
            defaults.setMerge(merge);
    
            String autowire = root.getAttribute(DEFAULT_AUTOWIRE_ATTRIBUTE);
            if (isDefaultValue(autowire)) {
                // Potentially inherited from outer <beans> sections, otherwise falling back to 'no'.
                autowire = (parentDefaults != null ? parentDefaults.getAutowire() : AUTOWIRE_NO_VALUE);
            }
            defaults.setAutowire(autowire);
    
            if (root.hasAttribute(DEFAULT_AUTOWIRE_CANDIDATES_ATTRIBUTE)) {
                defaults.setAutowireCandidates(root.getAttribute(DEFAULT_AUTOWIRE_CANDIDATES_ATTRIBUTE));
            }
            else if (parentDefaults != null) {
                defaults.setAutowireCandidates(parentDefaults.getAutowireCandidates());
            }
    
            if (root.hasAttribute(DEFAULT_INIT_METHOD_ATTRIBUTE)) {
                defaults.setInitMethod(root.getAttribute(DEFAULT_INIT_METHOD_ATTRIBUTE));
            }
            else if (parentDefaults != null) {
                defaults.setInitMethod(parentDefaults.getInitMethod());
            }
    
            if (root.hasAttribute(DEFAULT_DESTROY_METHOD_ATTRIBUTE)) {
                defaults.setDestroyMethod(root.getAttribute(DEFAULT_DESTROY_METHOD_ATTRIBUTE));
            }
            else if (parentDefaults != null) {
                defaults.setDestroyMethod(parentDefaults.getDestroyMethod());
            }
    
            defaults.setSource(this.readerContext.extractSource(root));
        }

    2、判断当前profile是否被激活

      我们知道spring支持配置多个profile,可在beans标签的profile属性配置,然后在运行前动态指定spring.active.profile的。在java项目中,可以配置系统属性System.setProperty("spring.profiles.active","test"),web项目中配置ServletContext上下文参数指定,springboot中也可以通过spring.active.profile指定。

            //判断当前Beans节点是否是默认命名空间
            if (this.delegate.isDefaultNamespace(root)) {
                //获取beans节点的profile属性
                String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
                if (StringUtils.hasText(profileSpec)) {
                    //可以使用逗号或分号将当前beans标签指定为多个profile类型
                    String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                            profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
                    // We cannot use Profiles.of(...) since profile expressions are not supported
                    // in XML config. See SPR-12458 for details.
                    //判断当前beans标签的profile是否被激活
                    if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                                    "] not matching: " + getReaderContext().getResource());
                        }
                        return;
                    }
                }
            }

       知道了怎么用,就容易多了,首先得到beans标签的指定的profile数组,与指定的spring.active.profile对比,符合条件的话改beans才会被加载。

      首先通过getReaderContext().getEnvironment(),获取StandardEnvironment。这里的getReaderContext()就是获取的刚刚说的XmlReaderContext上下文,再从上下文中的得到XmlBeanDefinitionReader初始化时引用的StandardEnvironment。

      下面是XmlBeanDefinitionReader继承自抽象父类的构造方法。

        protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
            Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
            this.registry = registry;
    
            // Determine ResourceLoader to use.
            if (this.registry instanceof ResourceLoader) {
                this.resourceLoader = (ResourceLoader) this.registry;
            }
            else {
                this.resourceLoader = new PathMatchingResourcePatternResolver();
            }
    
            // Inherit Environment if possible
            if (this.registry instanceof EnvironmentCapable) {
                this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
            }
            else {
                this.environment = new StandardEnvironment();
            }
        }

       这里的StandardEnvironment初始化会加载当前的系统变量和环境变量,是对系统变量和环境变量的封装。在StandardEnvironmen继承自父类的构造方法中,会调用customizePropertySources方法

    private final MutablePropertySources propertySources = new MutablePropertySources()
    protected void customizePropertySources(MutablePropertySources propertySources) {
            propertySources.addLast(
                    new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
            propertySources.addLast(
                    new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
        }

      该方法将当前系统变量和环境变量保存在其propertySources属性中。

    public class MutablePropertySources implements PropertySources {
    
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
        /**
         * Create a new {@link MutablePropertySources} object.
         */
        public MutablePropertySources() {
        }
    }

      而MutablePropertySources 有一个List属性,保存多个属性来源。

      也就是说,StandardEnvironment初始化完成时,就会加载系统变量和环境变量,然后这里会调用acceptsProfiles(specifiedProfiles)方法判定当前beans标签的profile是否应该被加载,遍历给定的profiles数组,只要有一个被指定为spring.active.profile就返回true。

        public boolean acceptsProfiles(String... profiles) {
            Assert.notEmpty(profiles, "Must specify at least one profile");
            for (String profile : profiles) {
                if (StringUtils.hasLength(profile) && profile.charAt(0) == '!') {
                    if (!isProfileActive(profile.substring(1))) {
                        return true;
                    }
                }
                else if (isProfileActive(profile)) {
                    return true;
                }
            }
            return false;
        }
        protected boolean isProfileActive(String profile) {
            validateProfile(profile);
            Set<String> currentActiveProfiles = doGetActiveProfiles();
            return (currentActiveProfiles.contains(profile) ||
                    (currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile)));
        }

      重点是获取spring.active.profile的方法。

        protected Set<String> doGetActiveProfiles() {
            synchronized (this.activeProfiles) {
                if (this.activeProfiles.isEmpty()) {
                    //从propertySources中获取,key为spring.active.profile
                    String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
                    if (StringUtils.hasText(profiles)) {
                        setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
                                StringUtils.trimAllWhitespace(profiles)));
                    }
                }
                return this.activeProfiles;
            }
        }

    3、解析根节点parseBeanDefinitions

      经过了委托类的创建,spring.active.profile判断返回true的beans标签,才会进入到parseBeanDefinitions中被解析

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

      看到这里应该就一目了然了,判定root节点以及其子节点是否是默认标签,默认标签和自定义标签有不同的解析方式,除了beans、bean、alias和import四种标签外,都是自定义标签,自定义标签需要实现一些接口和配置。如果是默认标签,进入parseDefaultElement方法。

        private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
            //解析import标签
            if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
                importBeanDefinitionResource(ele);
            }
            //解析alias标签
            else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
                processAliasRegistration(ele);
            }
            //解析bean标签
            else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
                processBeanDefinition(ele, delegate);
            }
            //解析beans标签
            else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
                // recurse
                doRegisterBeanDefinitions(ele);
            }
        }

      具体的解析过程下一章会讲到,在这里,看看解析beans标签的时候,是不是又会调用doRegisterBeanDefinitions方法?还记得吗?这正是对之前该方法会递归调用的解释,再贴一遍代码吧。

        protected void doRegisterBeanDefinitions(Element root) {
            // Any nested <beans> elements will cause recursion in this method. In
            // order to propagate and preserve <beans> default-* attributes correctly,
            // keep track of the current (parent) delegate, which may be null. Create
            // the new (child) delegate with a reference to the parent for fallback purposes,
            // then ultimately reset this.delegate back to its original (parent) reference.
            // this behavior emulates a stack of delegates without actually necessitating one.
            BeanDefinitionParserDelegate parent = this.delegate;
            //委托给delegate解析
            this.delegate = createDelegate(getReaderContext(), root, parent);
    
            //判断当前Beans节点是否是默认命名空间
            if (this.delegate.isDefaultNamespace(root)) {
                //获取beans节点的profile属性
                String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
                if (StringUtils.hasText(profileSpec)) {
                    //可以使用逗号或分号将当前beans标签指定为多个profile类型
                    String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                            profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
                    // We cannot use Profiles.of(...) since profile expressions are not supported
                    // in XML config. See SPR-12458 for details.
                    //判断当前beans标签的profile是否被激活
                    if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                                    "] not matching: " + getReaderContext().getResource());
                        }
                        return;
                    }
                }
            }
            //解析前处理,留给子类实现
            preProcessXml(root);
            //真正的解析过程
            parseBeanDefinitions(root, this.delegate);
            //解析后处理,留给子类实现
            postProcessXml(root);
    
            this.delegate = parent;
        }

      走的太远,不要忘记为什么出发!

      参考:spring源码深度解析

  • 相关阅读:
    <记录> axios 模拟表单提交数据
    PHP 设计模式(一)
    CSS3中translate、transform和translation的区别和联系
    微信小程序 支付功能 服务器端(TP5.1)实现
    微信小程序 用户登录 服务器端(TP5.1)实现
    <记录> curl 封装函数
    <记录>TP5 关联模型使用(嵌套关联、动态排序以及隐藏字段)
    PHP/TP5 接口设计中异常处理
    TP5 自定义验证器
    高并发和大流量解决方案--数据库缓存
  • 原文地址:https://www.cnblogs.com/xiaohang123/p/12822669.html
Copyright © 2011-2022 走看看