Spring bean定义解析源码分析

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

在上一篇Spring IOC容器启动简介中在ClassPathXmlApplicationContext的基础粗略的分析了IOC容器的启动过程,对一些比较复杂的步骤没有详细的说明,从本篇开始对其中的一些比较复杂的步骤进行分析。本篇对基于ClassPathXmlApplicationContext的IOC容器的bean定义的解析与加载过程进行分析。bean定义解析加载的简单时序图如下:

20191102100510\_1.png

bean定义的解析通过XmlBeanDefinitionReader来完成,在解析前先做一些准备工作:1、设置环境变量(Environment)用来匹配在bean配置文件可能出现的一些占位符;2、设置资源定位器用来定位bean的xml定义文件;3、设置xml实体处理器辅助xml的解析工作。

    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

    // Configure the bean definition reader with this context's
    // resource loading environment.
    beanDefinitionReader.setEnvironment(this.getEnvironment());
    beanDefinitionReader.setResourceLoader(this);
    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

XmlBeanDefinitionReader通过DocumentLoader把bean的定义文件解析成一颗DOM树,然后对DOM中的各个结点就行解析。

XmlBeanDefinitionReader的doLoadBeanDefinitions方法

    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
        try {
            int validationMode = getValidationModeForResource(resource);
            Document doc = this.documentLoader.loadDocument(
                    inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
            return registerBeanDefinitions(doc, resource);
        }
        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);
        }
    }

接下来是对DOM树根节点中的各个结点进行解析:

首先解析检查是否存在profile属性,如果定义了profile属性,判断该profile和设置到坏境中的profile是否匹配,如果不匹配说明这颗子树定义的bean不适用于当前的运行坏境,跳过当前整个颗子树的解析,见DefaultBeanDefinitionDocumentReader类的doRegisterBeanDefinitions方法中的代码片段

    String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
    if (StringUtils.hasText(profileSpec)) {
        Assert.state(this.environment != null, "Environment must be set for evaluating profiles");
        String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
        if (!this.environment.acceptsProfiles(specifiedProfiles)) {
            return;
        }
    }

循环各个子树,检查节点是默认的名称空间还是扩展的名称空间,比如是默认的名称空间,而这种标签是扩展的标签需要额外的标签处理器,默认标签的一级标签是,先看一下默认名称空间。

默认名称空间有几个二级标签import、alias、bean、beans。

import标签,读取resource属性,如果未设置解析报错,判断resource对应的路径是绝对路径还是相对路径,如果是相对路径需要把路径装换成完整的路径,然后递归调用loadBeanDefinitions

beans标签,直接递归调用doRegisterBeanDefinitions

alias标签,读取name和alias属性,把该别名映射注册到容器中

代码在DefaultBeanDefinitionDocumentReader的processAliasRegistration方法

    protected void processAliasRegistration(Element ele) {
            String name = ele.getAttribute(NAME_ATTRIBUTE);
            String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
            boolean valid = true;
            if (!StringUtils.hasText(name)) {
                getReaderContext().error("Name must not be empty", ele);
                valid = false;
            }
            if (!StringUtils.hasText(alias)) {
                getReaderContext().error("Alias must not be empty", ele);
                valid = false;
            }
            if (valid) {
                try {
                    getReaderContext().getRegistry().registerAlias(name, alias);
                }
                catch (Exception ex) {
                    getReaderContext().error("Failed to register alias '" + alias +
                            "' for bean with name '" + name + "'", ele, ex);
                }
                getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
            }
        }

bean标签,解析bean标签下的子树是整个默认名称空间标签解析最核心的动作,import和beans标签最后也会走到这一步。bean标签的解析逻辑被委托给BeanDefinitionParserDelegate:

首先读取bean的id属性和name属性,分别代码bean的id和别名,name如果以逗号或分号分隔,分裂成别名数组。如果id没有设置,取第一个别名当做id,并且把这个别名从别名数组中删除。

    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
        String id = ele.getAttribute(ID_ATTRIBUTE);
        String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

        List<String> aliases = new ArrayList<String>();
        if (StringUtils.hasLength(nameAttr)) {
            String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            aliases.addAll(Arrays.asList(nameArr));
        }

        String beanName = id;
        if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
            beanName = aliases.remove(0);
            if (logger.isDebugEnabled()) {
                logger.debug("No XML 'id' specified - using '" + beanName +
                        "' as bean name and " + aliases + " as aliases");
            }
        }

        ...

    }

如果bean不是内部bean,检查id和别名是否已经被其它的bean使用,如果已经被占用,解析报错。否则把id和别名都放到一个usedNames集合属性中,声明这些id和别名已经被占用。之所以内部bean不用检查时因为内部bean的id是容器生成的,容器会保证不会发生重复。parseBeanDefinitionElement等方法中有一个containingBean参数,这个参数用来区分当前解析的bean是否是内部bean,如果是非空值说明是内部bean,内部bean和非内部bean解析时只做了两件不同的事情:1、id生成策略;2、内部bean需要继承其外部bean的scope。

    protected void checkNameUniqueness(String beanName, List<String> aliases, Element beanElement) {
        String foundName = null;

        if (StringUtils.hasText(beanName) && this.usedNames.contains(beanName)) {
            foundName = beanName;
        }
        if (foundName == null) {
            foundName = (String) CollectionUtils.findFirstMatch(this.usedNames, aliases);
        }
        if (foundName != null) {
            error("Bean name '" + foundName + "' is already used in this <beans> element", beanElement);
        }

        this.usedNames.add(beanName);
        this.usedNames.addAll(aliases);
    }

如果既没有设置id也没有设置别名,那么容器会给bean自动生成一个id,生成规则如下:

1、在定义bean时是否指定了class,如果指定了取类名,如果未指定,判断是否指定了parent,如果指定了取parent名+$child,如果未指定判断是否指定了factory bean,如果指定了取factory bean名+$created,如果都未指定说明配置有错误
2、如果是内部bean,取步骤一生成的名称+#+随机数
3、如果不是内部bean,如果步骤一生成的名称在容器中不存在,取步骤一生成的名称,否则取步骤一生成的名称+#计数,如果计数已经被使用再次计数直到计数未被使用为止

    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {

        ...

        if (beanDefinition != null) {
            if (!StringUtils.hasText(beanName)) {
                try {
                    if (containingBean != null) {
                        beanName = BeanDefinitionReaderUtils.generateBeanName(
                                beanDefinition, this.readerContext.getRegistry(), true);
                    }
                    else {
                        beanName = this.readerContext.generateBeanName(beanDefinition);
                        // Register an alias for the plain bean class name, if still possible,
                        // if the generator returned the class name plus a suffix.
                        // This is expected for Spring 1.2/2.0 backwards compatibility.
                        String beanClassName = beanDefinition.getBeanClassName();
                        if (beanClassName != null &&
                                beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
                                !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                            aliases.add(beanClassName);
                        }
                    }
                    if (logger.isDebugEnabled()) {
                        logger.debug("Neither XML 'id' nor 'name' specified - " +
                                "using generated bean name [" + beanName + "]");
                    }
                }
                catch (Exception ex) {
                    error(ex.getMessage(), ele);
                    return null;
                }
            }

            ...
        }

        return null;
    }

    public static String generateBeanName(
            BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean)
            throws BeanDefinitionStoreException {

        String generatedBeanName = definition.getBeanClassName();
        if (generatedBeanName == null) {
            if (definition.getParentName() != null) {
                generatedBeanName = definition.getParentName() + "$child";
            }
            else if (definition.getFactoryBeanName() != null) {
                generatedBeanName = definition.getFactoryBeanName() + "$created";
            }
        }
        if (!StringUtils.hasText(generatedBeanName)) {
            throw new BeanDefinitionStoreException("Unnamed bean definition specifies neither " +
                    "'class' nor 'parent' nor 'factory-bean' - can't generate bean name");
        }

        String id = generatedBeanName;
        if (isInnerBean) {
            // Inner bean: generate identity hashcode suffix.
            id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);
        }
        else {
            // Top-level bean: use plain class name.
            // Increase counter until the id is unique.
            int counter = -1;
            while (counter == -1 || registry.containsBeanDefinition(id)) {
                counter++;
                id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + counter;
            }
        }
        return id;
    }

接下来解析其它属性和子标签比如class、parent、scope、lazy-init、lookup-method等,生成一个GenericBeanDefinition对象,并且把这些属性封装到GenericBeanDefinition对象相应的属性中,代码在BeanDefinitionParserDelegate类的parseBeanDefinitionElement方法:

    public AbstractBeanDefinition parseBeanDefinitionElement(
            Element ele, String beanName, BeanDefinition containingBean) {

        this.parseState.push(new BeanEntry(beanName));

        String 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);
            }
            AbstractBeanDefinition bd = createBeanDefinition(className, parent);

            parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
            bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

            parseMetaElements(ele, bd);
            parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
            parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

            parseConstructorArgElements(ele, bd);
            parsePropertyElements(ele, bd);
            parseQualifierElements(ele, bd);

            bd.setResource(this.readerContext.getResource());
            bd.setSource(extractSource(ele));

            return bd;
        }
        catch (ClassNotFoundException ex) {
            error("Bean class [" + className + "] not found", ele, ex);
        }
        catch (NoClassDefFoundError err) {
            error("Class that bean class [" + className + "] depends on not found", ele, err);
        }
        catch (Throwable ex) {
            error("Unexpected failure during bean definition parsing", ele, ex);
        }
        finally {
            this.parseState.pop();
        }

        return null;
    }

最后如果定义了修饰器BeanDefinitionDecorator,对bean定义进行修改修饰,AOP标签定义了修饰器,这个在后面的文章介绍,然后把解析出来的bean定义和别名都注册到容器中。

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

    public static void registerBeanDefinition(
            BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
            throws BeanDefinitionStoreException {

        // Register bean definition under primary name.
        String beanName = definitionHolder.getBeanName();
        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

        // Register aliases for bean name, if any.
        String[] aliases = definitionHolder.getAliases();
        if (aliases != null) {
            for (String aliase : aliases) {
                registry.registerAlias(beanName, aliase);
            }
        }
    }

以上是默认名称空间的标签解析,如果名称DOM子树根节点的名称空间是扩展的,那么进入BeanDefinitionParserDelegate的parseCustomElement方法,大致时序图如下:

20191102100510\_2.png

首先需要查找一个名称空间处理器,代码如下:

    public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";

    public DefaultNamespaceHandlerResolver() {
        this(null, DEFAULT_HANDLER_MAPPINGS_LOCATION);
    }

    private Map<String, Object> getHandlerMappings() {
        if (this.handlerMappings == null) {
            synchronized (this) {
                if (this.handlerMappings == null) {
                    try {
                        //handlerMappingsLocation=DEFAULT_HANDLER_MAPPINGS_LOCATION
                        Properties mappings =
                                PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
                        if (logger.isDebugEnabled()) {
                            logger.debug("Loaded NamespaceHandler mappings: " + mappings);
                        }
                        Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
                        CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
                        this.handlerMappings = handlerMappings;
                    }
                    catch (IOException ex) {
                        throw new IllegalStateException(
                                "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
                    }
                }
            }
        }
        return this.handlerMappings;
    }

名称空间和名称空间处理器的映射关系定义在jar包的META-INF/spring.handlers文件中,比如spring-context-3.2.9.RELEASE.jar的spring.handlers的内容如下:

    http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
    http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
    http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
    http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
    http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler

我们可以看到context的名称空间对应的处理是ContextNamespaceHandler,jee名称空间的处理器是JeeNamespaceHandler等。框架把所有Spring相关jar包中的spring.handlers文件加载出来并且存储名称空间和处理器的映射关系。找到处理器之后调用处理器的parse方法。

    public BeanDefinition parse(Element element, ParserContext parserContext) {
        return findParserForElement(element, parserContext).parse(element, parserContext);
    }

    private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
        String localName = parserContext.getDelegate().getLocalName(element);
        BeanDefinitionParser parser = this.parsers.get(localName);
        if (parser == null) {
            parserContext.getReaderContext().fatal(
                    "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
        }
        return parser;
    }

从上面代码可以看出首先定位出待解析的标签对应的解析器,以ContextNamespaceHandler为例,从ContextNamespaceHandler的代码可以看出context名称空间下所有标签的解析器。

    public class ContextNamespaceHandler extends NamespaceHandlerSupport {

        public void init() {
            registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
            registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
            registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
            registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
            registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
            registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
            registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
            registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
        }

    }

找到解析器之后再调用解析器的parse方法解析具体的标签,比如annotation-config标签的解析器是AnnotationConfigBeanDefinitionParser,代码如下,在解析时遇到annotation-config标签时会注册ConfigurationClassPostProcessor、AutowiredAnnotationBeanPostProcessor等BPP到容器中。

    public class AnnotationConfigBeanDefinitionParser implements BeanDefinitionParser {

        public BeanDefinition parse(Element element, ParserContext parserContext) {
            Object source = parserContext.extractSource(element);

            // Obtain bean definitions for all relevant BeanPostProcessors.
            Set<BeanDefinitionHolder> processorDefinitions =
                    AnnotationConfigUtils.registerAnnotationConfigProcessors(parserContext.getRegistry(), source);

            // Register component for the surrounding <context:annotation-config> element.
            CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
            parserContext.pushContainingComponent(compDefinition);

            // Nest the concrete beans in the surrounding component.
            for (BeanDefinitionHolder processorDefinition : processorDefinitions) {
                parserContext.registerComponent(new BeanComponentDefinition(processorDefinition));
            }

            // Finally register the composite component.
            parserContext.popAndRegisterContainingComponent();

            return null;
        }

    }

关于扩展名称空间下的标签就不一个个分析,知道如何找相关的代码就好。


来源:http://ddrv.cn

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » Spring bean定义解析源码分析

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏