蹲厕所的熊

benjaminwhx

Spring之如何解析BeanFactory中的bean

2018-08-11 作者: 吴海旭


  1. 容器的基本实现
    1. 基本用法
    2. 结构图
  2. 源码分析
    1. XmlBeanFactory
    2. XmlBeanDefinitionReader
      1. 验证模式的获取
      2. 获取Document
      3. EntityResolver的用法
    3. 解析及注册BeanDefinitions
      1. profile属性的使用
      2. 解析并注册BeanDefinition
      3. 系统默认标签的解析:parseDefaultElement
        1. bean标签的解析和注册
          1. 解析BeanDefinition
          2. 装饰BeanDefinition
          3. 注册BeanDefinition
          4. 通知监听器解析及注册完成
        2. alias标签的解析
        3. import标签的解析
        4. 嵌入式beans标签的解析
      4. 自定义标签的解析:parseCustomElement
  3. 总结

容器的基本实现

作为各大公司的主流框架Spring,它提供了一系列核心的功能,依赖注入则是这些功能中最为重要的。而注入必须要先解析并管理众多的Bean,本文将通过BeanFactory的实现类XmlBeanFactory来一步步的带你了解bean是如何被解析的。

基本用法

任何一个类都可以作为一个bean来被Spring管理,我们先定义一个类:MyTestBean

public class MyTestBean {
    private String testStr = "testStr";

    public String getTestStr() {
        return testStr;
    }

    public void setTestStr(String testStr) {
        this.testStr = testStr;
    }
}

接着,把它变成一个由Spring管理的bean:

<?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-3.1.xsd">

    <bean id="myTestBean" class="com.github.MyTestBean" />
</beans>

在上面的配置中我们看到了bean的声明方式。接着,我们来尝试获取该bean:

public class XmlBeanFactoryTest {

    public static void main(String[] args) {
        BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("com/github/spring-bean.xml"));
        System.out.println(beanFactory.getBean("myTestBean"));
    }
}

控制台的结果表明MyTestBean已经被Spring所管理,默认它是以单例的形式存在的。

结构图

XmlBeanFactory继承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册及加载bean的默认实现,而对于XmlBeanFactory与DefaultListableBeanFactory不同的地方其实是在XmlBeanFactory中使用了自定义的XML读取器XmlBeanDefinitionReader,实现了个性化的BeanDefinitionReader读取,DefaultListableBeanFactory继承了AbstractAutowireCapableBeanFactory并实现了ConfigurableListableBeanFactory以及BeanDefinitionRegistry接口。以下是XmlBeanFactory的继承关系结构图:

XmlBeanFactory

简单说一下图中各个类的作用:

  • AliasRegistry:定义对alias的简单增删改等操作。
  • SimpleAliasRegistry:主要使用map作为alias的缓存,并对接口AliasRegistry进行实现。
  • BeanDefinitionRegistry:定义对BeanDefinition的各种增删改操作。
  • BeanFactory:定义获取bean及bean的各种属性。
  • HierarchicalBeanFactory:继承BeanFactory,也就是在BeanFactory定义的功能的基础上增加了对parentFactory的支持。
  • ListableBeanFactory:根据各种条件获取bean的配置清单。
  • ConfigurableBeanFactory:根据配置Factory的各种方法。
  • SingletonBeanRegistry:定义对单例的注册及获取。
  • DefaultSingletonBeanRegistry:对接口SingletonBeanRegistry各函数的实现。
  • FactoryBeanRegistrySupport:在DefaultSingletonBeanRegistry基础上增加了对FactoryBean的特殊处理功能。
  • AbstractBeanFactory:综合FactoryBeanRegistrySupport和ConfigurableBeanFactory的功能。
  • AutowireCapableBeanFactory:提供创建bean,自动注入,初始化以及应用bean的后处理器。
  • AbstractAutowireCapableBeanFactory:综合AbstractBeanFactory并对接口AutowireCapableBeanFactory进行实现。
  • ConfigurableListableBeanFactory:BeanFactory配置清单,指定忽略类型及接口等。
  • DefaultListableBeanFactory:综合上面所有功能,主要是对Bean注册后的处理。

XmlBeanFactory对DefaultListableBeanFactory类进行了扩展,主要用于从XML文档中读取BeanDefinition,对于注册及获取Bean都是使用从父类DefaultListableBeanFactory继承的方法去实现,而唯独与父类不同的个性化实现就是增加了XmlBeanDefinitionReader类型的reader属性。在XmlBeanFactory中主要使用reader属性对资源文件进行读取和注册。

源码分析

好了,我们通过XmlBeanFactory的源码一步步揭开它的神秘面纱吧~

注:因异常处理和日志打印的代码比较多,下面会把所有的异常处理和日志打印的代码忽略,只保留核心的处理流程。

XmlBeanFactory

点开XmlBeanFactory的源码,映入眼帘的只有它的两个构造方法和一个reader属性:

public class XmlBeanFactory extends DefaultListableBeanFactory {

    private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

    public XmlBeanFactory(Resource resource) throws BeansException {
        this(resource, null);
    }

    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
        super(parentBeanFactory);
        // 通过reader加载bean
        this.reader.loadBeanDefinitions(resource);
    }
}

在Spring中,所有的资源都是通过Resource来定义的,它通过一系列的方法描述了资源,并且它还能够获取到资源的输入流,因为之前详细的说过 Spring的资源管理,这里就不赘述了。

构造函数接把收到的Resource(我们定义bean的spring配置文件)交给reader去处理。可以试着猜想一下loadBeanDefinitions要做的事:

  • 读取Resource文件并找到一个可以解析该文件的类。
  • 解析文件里的bean标签,并把它转化成一个真正的Bean类。
  • 实例化Bean并把它放入Spring的上下文中,供之后使用。

虽然大体的思路是这样,但绝不是仅仅这么简单的过程,我们继续往下进入XmlBeanDefinitionReader类。

XmlBeanDefinitionReader

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    // 把Resource用EncodedResource包装,这里因为没有指定编码所以和Resource没区别
    return loadBeanDefinitions(new EncodedResource(resource));
}

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    // threadLocal记录已经加载的资源
    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    if (currentResources == null) {
        currentResources = new HashSet<EncodedResource>(4);
        this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }
    // 把encodedResource放入set集合中
    if (!currentResources.add(encodedResource)) {
        throw new BeanDefinitionStoreException(
                "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    }
    try {
        InputStream inputStream = encodedResource.getResource().getInputStream();
        try {
            // InputSource这个类是org.xml.sax包下的,用来解析xml文件使用
            InputSource inputSource = new InputSource(inputStream);
            if (encodedResource.getEncoding() != null) {
                inputSource.setEncoding(encodedResource.getEncoding());
            }
            // 核心加载逻辑
            return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
        }
        finally {
            inputStream.close();
        }
    } catch (IOException ex) {
        throw new BeanDefinitionStoreException(
                "IOException parsing XML document from " + encodedResource.getResource(), ex);
    } finally {
        // 资源清理
        currentResources.remove(encodedResource);
        if (currentResources.isEmpty()) {
            this.resourcesCurrentlyBeingLoaded.remove();
        }
    }
}

接着来看核心的加载逻辑:

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
        throws BeanDefinitionStoreException {
    // org.w3c.dom.Document
    Document doc = doLoadDocument(inputSource, resource);
    return registerBeanDefinitions(doc, resource);
}

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
    return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
            getValidationModeForResource(resource), isNamespaceAware());
}

doLoadDocument会资源解析成w3c规范的Document类,之后进行真正的解析以及注册。

验证模式的获取

在doLoadDocument中会通过getValidationModeForResource方法获取到XML文件的验证模式:

protected int getValidationModeForResource(Resource resource) {
    int validationModeToUse = getValidationMode();
    if (validationModeToUse != VALIDATION_AUTO) {
        return validationModeToUse;
    }
    // 如果未指定则使用自动检测
    int detectedMode = detectValidationMode(resource);
    if (detectedMode != VALIDATION_AUTO) {
        return detectedMode;
    }
    return VALIDATION_XSD;
}

有兴趣的同学可以先去了解一下DTD和XSD的区别,然后再来分析这一段代码就非常容易理解了。方法的实现还是很简单的,无非是如果设定了验证模式则优先使用指定的验证模式,否则使用自动检测的方式。而自动检测验证模式的功能是在函数detectValidationMode方法中实现的。

protected int detectValidationMode(Resource resource) {
    // 使用XML校验模式探测器来检查resource是哪种类型的XML
    return this.validationModeDetector.detectValidationMode(resource.getInputStream());
}

public int detectValidationMode(InputStream inputStream) throws IOException {
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
    try {
        boolean isDtdValidated = false;
        String content;
        while ((content = reader.readLine()) != null) {
            content = consumeCommentTokens(content);
            // 如果读取的行是空或者是注释则略过
            if (this.inComment || !StringUtils.hasText(content)) {
                continue;
            }
            if (hasDoctype(content)) {
                isDtdValidated = true;
                break;
            }
            // 读取到<开始符号,验证模式一定会在开始符号之前
            if (hasOpeningTag(content)) {
                // End of meaningful data...
                break;
            }
        }
        return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
    } catch (CharConversionException ex) {
        return VALIDATION_AUTO;
    } finally {
        reader.close();
    }
}

只要我们理解了XSD和DTD的使用方法,理解上面的代码应该不会太难,Spring用来检测验证模式的办法就是判断是否包含DOCTYPE,如果包含就是DTD,否则就是XSD。

获取Document

回到上面doLoadDocument的逻辑,它会委托DefaultDocumentLoader进行document的加载。

public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
                             ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

    DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
    DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
    return builder.parse(inputSource);
}

对于这部分代码其实并没有太多可以描述的,因为通过SAX解析XML文档的套路大致都差不多,Spring在这里并没有什么特殊的地方,同样首先创建DocumentBuilderFactory,再通过DocumentBuilderFactory创建DocumentBuilder,进行解析inputSource来返回Document对象。

这里有一个很重要的参数 entityResolver ,这个参数是通过 getEntityResolver() 函数获取的返回值:

protected EntityResolver getEntityResolver() {
    if (this.entityResolver == null) {
        ResourceLoader resourceLoader = getResourceLoader();
        if (resourceLoader != null) {
            this.entityResolver = new ResourceEntityResolver(resourceLoader);
        }
        else {
            this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
        }
    }
    return this.entityResolver;
}

那么这个entityResolver有什么用呢?

EntityResolver的用法

关于什么是EntityResolver,官网给出了这样的解释:如果SAX应用程序需要实现自定义处理外部实体,则必须实现此接口并使用 setEntityResolver 方法向SAX驱动器注册一个实例,也就是说,对于解析一个XML,SAX首先读取该XML文档上的声明,根据声明去寻找对应的DTD定义,以便对文档进行一个验证。默认的寻找规则是通过网络(实现上就是声明的DTD的URI地址)来下载相应的DTD声明,并进行认证。下载的过程是一个漫长的过程,而且当网络中断或不可用时,这里会报错,就是因为相应的DTD声明没有被找到的原因。

EntityResolver的作用是项目本身就可以提供一个如何寻找DTD声明的方法,即由程序来实现寻找DTD声明的过程,比如我们将DTD文件放到项目中某处,在实现时直接将此文档读取并返回给SAX即可。这样就避免了通过网络来寻找相应的声明。

首先看entityResolver的接口方法声明:

public abstract InputSource resolveEntity (String publicId, String systemId);

它接收两个参数:publicId和systemId,并返回一个inputSource对象。这里我们以特定配置文件来进行讲解。

(1)如果我们在解析验证模式为XSD的配置文件:

<?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-3.1.xsd">
</beans>

会读取以下两个参数:

publicId: null
systemId: http://www.springframework.org/schema/beans/spring-beans-3.1.xsd

(2)如果我们在解析验证模式为DTD的配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//Spring//DTD BEAN 2.0//EN" "http://www.Springframework.org/dtd/Spring-beans-2.0.dtd">
<beans>
...
</beans>

会读取到以下两个参数:

publicId: -//Spring//DTD BEAN 2.0//EN
systemId: http://www.Springframework.org/dtd/Spring-beans-2.0.dtd

由于验证文件默认的加载方式是通过URL进行网络下载获取,这样会造成延迟,用户体验也不好,一般的做法都是将验证文件放置在自己的工程里,那么怎么做才能将这个URL转换为自己工程里对应的地址文件呢?

回到之前的 getEntityResolver 方法。方法中根据是否存在resourceLoader来分别实例化不同的EntityResolver。而ResourceEntityResolver是DelegatingEntityResolver的子类,最终都会调用DelegatingEntityResolver的构造函数:

public DelegatingEntityResolver(ClassLoader classLoader) {
    this.dtdResolver = new BeansDtdResolver();
    this.schemaResolver = new PluggableSchemaResolver(classLoader);
}

DelegatingEntityResolver只是一个代理类,真正处理resolveEntity的时候会根据不同的systemId路由到不同的类去处理:

public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
    if (systemId != null) {
        if (systemId.endsWith(DTD_SUFFIX)) {
            return this.dtdResolver.resolveEntity(publicId, systemId);
        }
        else if (systemId.endsWith(XSD_SUFFIX)) {
            return this.schemaResolver.resolveEntity(publicId, systemId);
        }
    }
    return null;
}

而这里最重要的schemaResolver(PluggableSchemaResolver)在构造函数中指定了XSD模式文件映射地址:META-INF/spring.schemas

public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas";

public PluggableSchemaResolver(ClassLoader classLoader) {
    this.classLoader = classLoader;
    this.schemaMappingsLocation = DEFAULT_SCHEMA_MAPPINGS_LOCATION;
}

在大多数的spring的jar包中都会发现这个文件的踪影,随便打开spring-aop的jar包,打开该文件,其中的内容为:

http\://www.springframework.org/schema/aop/spring-aop-2.0.xsd=org/springframework/aop/config/spring-aop-2.0.xsd
http\://www.springframework.org/schema/aop/spring-aop-2.5.xsd=org/springframework/aop/config/spring-aop-2.5.xsd
http\://www.springframework.org/schema/aop/spring-aop-3.0.xsd=org/springframework/aop/config/spring-aop-3.0.xsd
http\://www.springframework.org/schema/aop/spring-aop-3.1.xsd=org/springframework/aop/config/spring-aop-3.1.xsd
http\://www.springframework.org/schema/aop/spring-aop-3.2.xsd=org/springframework/aop/config/spring-aop-3.2.xsd
http\://www.springframework.org/schema/aop/spring-aop-4.0.xsd=org/springframework/aop/config/spring-aop-4.0.xsd
http\://www.springframework.org/schema/aop/spring-aop-4.1.xsd=org/springframework/aop/config/spring-aop-4.1.xsd
http\://www.springframework.org/schema/aop/spring-aop-4.2.xsd=org/springframework/aop/config/spring-aop-4.2.xsd
http\://www.springframework.org/schema/aop/spring-aop-4.3.xsd=org/springframework/aop/config/spring-aop-4.3.xsd
http\://www.springframework.org/schema/aop/spring-aop.xsd=org/springframework/aop/config/spring-aop-4.3.xsd

解析及注册BeanDefinitions

当把文件转换为Document后,接下来就是最重要的提取以及注册bean的方法 registerBeanDefinitions

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    // 使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    // 获取已经注册的bean数
    int countBefore = getRegistry().getBeanDefinitionCount();
    // 加载及注册bean
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    // 记录本次加载的BeanDefinition个数
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

继续往下进入registerBeanDefinitions方法:

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

方法很简单,仅仅提取出document的root元素交给下一个方法处理:

protected void doRegisterBeanDefinitions(Element root) {
    // 供<beans>标签递归调用
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);

    if (this.delegate.isDefaultNamespace(root)) {
        // 解析profile属性
        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)) {
                return;
            }
        }
    }

    // 解析前处理,留给子类实现
    preProcessXml(root);
    parseBeanDefinitions(root, this.delegate);
    // 解析后处理,留给子类实现
    postProcessXml(root);

    this.delegate = parent;
}

profile属性的使用

profile属性可能我们用的比较少,我们用一个官方的例子来说明:

<?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-3.1.xsd">

    <beans profile="dev">
        ...
    </beans>

    <beans profile="production">
        ...
    </beans>
</beans>

集成到web开发环境时,在web.xml中加入以下代码:

<context-param>
    <param-name>Spring.profiles.active</param-name>
    <param-value>dev</param-value>
</context-param>

有了这个特性我们就可以同时在配置文件中部署两套配置来适用于生产环境和开发环境了。

解析并注册BeanDefinition

处理了profile后就可以进行XML的读取了,跟踪代码进入parseBeanDefinitions:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    // 对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)) {
                    // 对bean的处理
                    parseDefaultElement(ele, delegate);
                }
                else {
                    // 对bean的处理
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        delegate.parseCustomElement(root);
    }
}

在Spring的XML配置中有两大类Bean的声明,一个是默认的,如:

<bean id="test" class="xx.xx" />

另一类就是自定义的,如:

<tx:annotation-driven />

而上面对于bean的不同流程的处理就是针对这些情况来考虑的。

系统默认标签的解析:parseDefaultElement

进入到普通bean标签的解析方法:

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

bean标签的解析和注册

在4种标签的解析中,对bean标签的解析最为复杂也最为重要,所以我们从此标签开始深入分析,如果能理解此标签的解析过程,其他标签的解析自然会迎刃而解。首先我们进入processBeanDefinition方法:

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) {
        }
        // Send registration event.
        getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
}

乍一看似乎一头雾水,没有以前的函数那样清晰的逻辑,大致的逻辑总结如下:

  1. 首先委托BeanDefinitionDelegate类的parseBeanDefinitionElement方法进行元素解析,返回bdHolder,它包括配置文件中配置的各种属性了,例如class、name、id、alias之类的属性。
  2. 当返回的bdHolder不为空的情况下若存在默认标签的子节点下再有自定义属性,还需要再次对自定义标签进行解析。
  3. 解析完成后,需要对解析后的bdHolder进行注册。
  4. 最后发出响应事件,通知相关的监听器,这个bean已经加载完成了。
解析BeanDefinition

首先从元素解析及信息提取开始,进入parseBeanDefinitionElement方法:

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
    return parseBeanDefinitionElement(ele, null);
}

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
    // 解析id属性
    String id = ele.getAttribute(ID_ATTRIBUTE);
    // 解析name属性
    String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

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

    // id不存在取name属性作为bean的name
    String beanName = id;
    if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
        beanName = aliases.remove(0);
    }

    if (containingBean == null) {
        // 检查name值是否在当前所在的beans元素中存在
        checkNameUniqueness(beanName, aliases, ele);
    }

    AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
    if (beanDefinition != null) {
        if (!StringUtils.hasText(beanName)) {
            try {
                // 如果不存在beanName
                if (containingBean != null) {
                    beanName = BeanDefinitionReaderUtils.generateBeanName(
                            beanDefinition, this.readerContext.getRegistry(), true);
                }
                else {
                    beanName = this.readerContext.generateBeanName(beanDefinition);
                    String beanClassName = beanDefinition.getBeanClassName();
                    if (beanClassName != null &&
                            beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
                            !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                        aliases.add(beanClassName);
                    }
                }
            }
            catch (Exception ex) {
                error(ex.getMessage(), ele);
                return null;
            }
        }
        String[] aliasesArray = StringUtils.toStringArray(aliases);
        return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
    }

    return null;
}

以上便是对默认标签解析的全过程了。当然,对Spring的解析犹如洋葱剥皮一样,一层一层的进行,尽管现在只能看到对属性id以及name的解析,但是很庆幸,思路我们已经了解了。

我们进一步查看解析BeanDefinition的核心方法:

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

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

    String className = null;
    // 解析class属性
    if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
        className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
    }

    try {
        String parent = null;
        // 解析parent属性
        if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
            parent = ele.getAttribute(PARENT_ATTRIBUTE);
        }
        // 创建用于承载属性的AbstractBeanDefinition类型的GenericBeanDefinition
        AbstractBeanDefinition bd = createBeanDefinition(className, parent);

        // 解析默认bean的各种属性
        parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
        // 提取description
        bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

        // 解析元数据
        parseMetaElements(ele, bd);
        // 解析lookup-method属性
        parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
        // 解析replaced-method属性
        parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

        // 解析构造函数参数
        parseConstructorArgElements(ele, bd);
        // 解析property子元素
        parsePropertyElements(ele, bd);
        // 解析qualifier子元素
        parseQualifierElements(ele, bd);

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

        return bd;
    }
    catch (Exception ex) {
        error("Bean class [" + className + "] not found", ele, ex);
    }
    finally {
        this.parseState.pop();
    }

    return null;
}

终于,bean标签的所有属性,不论常用的还是不常用的我们都看到了,尽管有些复杂的属性还需要进一步的解析,不过丝毫不会影响我们兴奋的心情,接下来,我们继续createBeanDefinition方法的分析。

protected AbstractBeanDefinition createBeanDefinition(String className, String parentName)
        throws ClassNotFoundException {

    return BeanDefinitionReaderUtils.createBeanDefinition(
            parentName, className, this.readerContext.getBeanClassLoader());
}

public static AbstractBeanDefinition createBeanDefinition(
        String parentName, String className, ClassLoader classLoader) throws ClassNotFoundException {

    GenericBeanDefinition bd = new GenericBeanDefinition();
    // parentName可能为空
    bd.setParentName(parentName);
    if (className != null) {
        if (classLoader != null) {
            bd.setBeanClass(ClassUtils.forName(className, classLoader));
        }
        else {
            bd.setBeanClassName(className);
        }
    }
    return bd;
}

接着进入parseBeanDefinitionAttributes方法解析默认bean的各种属性,因为方法太长就不贴出来了,主要解析了以下几个属性的值放入AbstractBeanDefinition中:

  • scope属性
  • singleton属性
  • abstract属性
  • lazy-init属性
  • autowire属性
  • dependency-check属性
  • depends-on属性
  • autowire-candidate属性
  • primary属性
  • init-method属性
  • destroy-method属性
  • factory-method属性
  • factory-bean属性

除了解析基本属性以外其他还有解析构造函数参数、property子元素等其他方法,思路都很清晰,这里就直接跳过了。有需要的可以自行查看源码。

装饰BeanDefinition

回到processBeanDefinition方法中去,上面解析完BeanDefinition后,调用了BeanDefinitionParserDelegate的decorateBeanDefinitionIfRequired方法,从语义上来讲的话就是对beanDefinition进行装饰,那么这句代码到底实现了什么样的功能呢?

其实这句代码适用于这样的场景:

<!-- spring的aop标签 -->
<bean id="test" class="test.MyClass">
    <aop:scoped-proxy/>
</bean>

<!-- spring的p标签 -->
<bean id="person" class="com.myclass.Person" p:age="21" p:tool-ref="tool"/>

当Spring中的bean使用的是默认的标签配置,但是其中的子元素却使用了自定义的配置时,这句代码便会起作用了。好了,我们继续分析下这段代码的逻辑。

public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) {
    return decorateBeanDefinitionIfRequired(ele, definitionHolder, null);
}

public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
        Element ele, BeanDefinitionHolder definitionHolder, BeanDefinition containingBd) {

    BeanDefinitionHolder finalDefinition = definitionHolder;

    // 遍历所有的属性,看看是否有适用于修饰的属性
    NamedNodeMap attributes = ele.getAttributes();
    for (int i = 0; i < attributes.getLength(); i++) {
        Node node = attributes.item(i);
        finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
    }

    // 遍历所有的子元素,看看是否有适用于修饰的子元素
    NodeList children = ele.getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
        Node node = children.item(i);
        if (node.getNodeType() == Node.ELEMENT_NODE) {
            finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
        }
    }
    return finalDefinition;
}

上面的代码,我们看到函数分别对元素的所有属性以及子节点调用了decorateIfRequired函数

public BeanDefinitionHolder decorateIfRequired(
        Node node, BeanDefinitionHolder originalDef, BeanDefinition containingBd) {

    // 获取自定义标签的命名空间
    String namespaceUri = getNamespaceURI(node);
    // 对于非默认标签进行修饰
    if (!isDefaultNamespace(namespaceUri)) {
        // 根据命名空间找到对应的处理器
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler != null) {
            // 进行修饰处理
            return handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
        }
    }
    return originalDef;
}

这里是如何根据命名空间找到对应的处理器的呢?还记得readerContext是在哪里初始化的吗?贴出之前的代码重新回忆一下:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    // 使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    // 获取已经注册的bean数
    int countBefore = getRegistry().getBeanDefinitionCount();
    // 加载及注册bean
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    // 记录本次加载的BeanDefinition个数
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

对了,它是在文件转换为Document后进行注册BeanDefinition的时候创建的,进入createReaderContext方法:

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

这里的getNamespaceHandlerResolver就是它获取命名空间处理器的核心方法:

public NamespaceHandlerResolver getNamespaceHandlerResolver() {
    if (this.namespaceHandlerResolver == null) {
        this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
    }
    return this.namespaceHandlerResolver;
}

protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
    return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());
}

NamespaceHandlerResolver最终就是DefaultNamespaceHandlerResolver的实例化。

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

public DefaultNamespaceHandlerResolver(ClassLoader classLoader) {
    this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}

可以看到,和之前XML的EntityResolver处理逻辑一样,它在META-INF下定义了spring.handlers文件,我们打开spring-beans的jar包下的spring.handlers文件:

http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler

这里根据不同的命名空间指定了不同的处理类,最后来看看DefaultNamespaceHandlerResolver的resolve方法:

public NamespaceHandler resolve(String namespaceUri) {
    // 加载META-INF/spring.handlers文件并根据key、value的方式解析放入map中
    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 {
            Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
            if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
                throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
                        "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
            }
            // 实例化命名空间处理器
            NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
            namespaceHandler.init();
            // 放入缓存中
            handlerMappings.put(namespaceUri, namespaceHandler);
            return namespaceHandler;
        }
        catch (ClassNotFoundException ex) {
        }
        catch (LinkageError err) {
        }
    }
}

这样装饰的逻辑都能够解释的清楚了。

注册BeanDefinition

对于配置文件,解析和装饰都结束了,得到的beanDefinition已经可以满足后续的使用要求了,唯一还剩下的工作就是注册了,也就是BeanDefinitionReaderUtils的registerBeanDefinition方法。

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

    // 使用beanName作为唯一标识注册
    String beanName = definitionHolder.getBeanName();
    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

    // 注册所有的别名
    String[] aliases = definitionHolder.getAliases();
    if (aliases != null) {
        for (String alias : aliases) {
            registry.registerAlias(beanName, alias);
        }
    }
}

注册的beanDefinition分为beanName和别名的方式分别注册到registry中,先来看beanName注册的方式。

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
        throws BeanDefinitionStoreException {
    if (beanDefinition instanceof AbstractBeanDefinition) {
        try {
            /**
             * 注册前的最后一次校验,这里的校验不同于之前的XML文件校验
             * 主要是对于AbstractBeanDefinition属性中的methodOverrides校验
             * 校验methodOverrides是否与工厂方法并存或者methodOverrides对应的方法根本不存在
             */
            ((AbstractBeanDefinition) beanDefinition).validate();
        }
        catch (BeanDefinitionValidationException ex) {
        }
    }

    BeanDefinition oldBeanDefinition;

    oldBeanDefinition = this.beanDefinitionMap.get(beanName);
    if (oldBeanDefinition != null) {
        if (!isAllowBeanDefinitionOverriding()) {
            // ...
        } else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
            // ...
        } else if (!beanDefinition.equals(oldBeanDefinition)) {
            // ...
        } else {
            // ...
        }
        // 注册beanDefinition
        this.beanDefinitionMap.put(beanName, beanDefinition);
    }
    else {
        if (hasBeanCreationStarted()) {
            // 如果beanDefinitin已经创建了,这里要使用同步块防止并发问题
            synchronized (this.beanDefinitionMap) {
                this.beanDefinitionMap.put(beanName, beanDefinition);
                List<String> updatedDefinitions = new ArrayList<String>(this.beanDefinitionNames.size() + 1);
                updatedDefinitions.addAll(this.beanDefinitionNames);
                updatedDefinitions.add(beanName);
                // 手动覆盖
                this.beanDefinitionNames = updatedDefinitions;
                if (this.manualSingletonNames.contains(beanName)) {
                    Set<String> updatedSingletons = new LinkedHashSet<String>(this.manualSingletonNames);
                    updatedSingletons.remove(beanName);
                    // 手动覆盖
                    this.manualSingletonNames = updatedSingletons;
                }
            }
        }
        else {
            // 仍处于启动注册阶段
            this.beanDefinitionMap.put(beanName, beanDefinition);
            this.beanDefinitionNames.add(beanName);
            this.manualSingletonNames.remove(beanName);
        }
        this.frozenBeanDefinitionNames = null;
    }

    if (oldBeanDefinition != null || containsSingleton(beanName)) {
        // 重置所有beanName对应的缓存
        resetBeanDefinition(beanName);
    }
}

其实看下来注册beanDefinition的逻辑无非就是放入缓存,再加上检查是否已经存在的操作。理解了注册bean的原理后,理解注册别名的原理就容易多了。

public void registerAlias(String name, String alias) {
    // 如果beanName与alias相同的话不记录alias,并删除对应的alias
    if (alias.equals(name)) {
        this.aliasMap.remove(alias);
    }
    else {
        String registeredName = this.aliasMap.get(alias);
        if (registeredName != null) {
            // 已存在不需要再注册
            if (registeredName.equals(name)) {
                return;
            }
            if (!allowAliasOverriding()) {
                // ...
            }
        }
        // 当A->B存在时,若再次出现A->C->B时则会抛出异常
        checkForAliasCircle(name, alias);
        // 注册alias
        this.aliasMap.put(alias, name);
    }
}
通知监听器解析及注册完成
public void fireComponentRegistered(ComponentDefinition componentDefinition) {
    this.eventListener.componentRegistered(componentDefinition);
}

这里的实现只为扩展,当程序开发人员需要对注册BeanDefinition事件进行监听时可以通过注册监听器的方式并将处理逻辑写入监听器中,目前在Spring中并没有对此事件做任何处理逻辑。

alias标签的解析

通过上面巨长无比的篇幅我们终于分析完了默认便签中对bean标签的处理,那么我们之前提到过,对配置文件的解析包括对import标签、alias标签、bean标签、beans标签的处理,现在我们已经完成了最重要也是最核心的功能,其他的解析步骤也都是围绕第3个解析而进行的。

在之前bean标签的解析里我们知道可以通过name属性来指定别名:

<bean id="testBean" name="testBean,testBean2" class="com.test" />

同样,alias标签也提供了bean的别名设置:

<bean id="testBean" class="com.test" />
<alias name="testBean" alias="testBean,testBean2" />

好了,进入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 {
            // 注册alias
            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中的alias解析大同小异,都是将别名与beanName组成一对注册至registry中,这里不再赘述。

import标签的解析

对于Spring配置文件的编写,我想,经历过庞大项目的人,都有那种恐惧的心理,太多的配置文件了。不过,分模块是大多数人能想到的方法,但是,怎么分模块,那就仁者见仁,智者见智了。使用import是个好方法,例如我们可以构造这样的Spring配置文件:

<beans>
    <import resource="customerContext.xml"/>
    <import resource="systemContext.xml"/>
</beans>

这样以后若有新模块加入,只需要简单修改这个文件,这样大大简化了配置后期维护的复杂度,并使配置模块化,易于管理。我们来看看Spring是如何解析import配置文件的呢?

protected void importBeanDefinitionResource(Element ele) {
    // 获取resource属性
    String location = ele.getAttribute(RESOURCE_ATTRIBUTE);

    // 解析系统属性: e.g. "${user.dir}"
    location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);

    Set<Resource> actualResources = new LinkedHashSet<Resource>(4);

    // 判断location是绝对url还是相对url
    boolean absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();

    if (absoluteLocation) {
        try {
            // 绝对url加载beanDefinition
            int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
        }
        catch (BeanDefinitionStoreException ex) {
        }
    }
    else {
        // 如果是相对url,根据url计算出绝对url
        try {
            int importCount;
            Resource relativeResource = getReaderContext().getResource().createRelative(location);
            if (relativeResource.exists()) {
                importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
                actualResources.add(relativeResource);
            } else {
                String baseLocation = getReaderContext().getResource().getURL().toString();
                importCount = getReaderContext().getReader().loadBeanDefinitions(
                        StringUtils.applyRelativePath(baseLocation, location), actualResources);
            }
        }
        catch (Exception ex) {
        }
    }
    // 解析后进行监听器激活处理
    Resource[] actResArray = actualResources.toArray(new Resource[actualResources.size()]);
    getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}

上面的代码不难,相信配合注释会很好理解,我们总结一下大致流程便于读者更好地梳理,在解析import标签时,Spring进行解析的步骤大致如下:

  1. 获取resource属性所表示的路径。
  2. 解析路径中的系统属性,格式如:”${user.dir}”
  3. 判定location是绝对路径还是相对路径。
  4. 如果是绝对路径则递归调用bean的解析过程,进行另一次的解析。
  5. 如果是相对路径则计算出绝对路径并进行解析。
  6. 通知监听器,解析完成。

嵌入式beans标签的解析

对于嵌入式的beans标签,相信大家使用过或者至少接触过,非常类似于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-3.1.xsd">

   <beans>
   </beans>
</beans>

对于嵌入式beans标签来讲,并没有太多可讲,与单独的配置文件并没有太大的差别,无非是递归调用beans的解析过程,相信大家根据之前讲解过的内容已经有能力理解其中的奥秘了。

自定义标签的解析:parseCustomElement

在默认标签解析一节中,装饰BeanDefinition时也会对自定义标签进行解析,相信好好看完那部分的会很容易理解下面的内容。

public BeanDefinition parseCustomElement(Element ele) {
    return parseCustomElement(ele, null);
}

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
    String namespaceUri = getNamespaceURI(ele);
    // 获取对应的命名空间handler
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if (handler == null) {
        return null;
    }
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

不论是Spring的自定义标签还是我们自己定义的标签,都要去实现NamespaceHandlerSupport类,并重写init方法。而parse方法则是NamespaceHandlerSupport中实现好了的。

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);
    // 从注册的parsers中获取对应的BeanDefinitionParser
    BeanDefinitionParser parser = this.parsers.get(localName);
    return parser;
}

这里的parsers是缓存自定义标签和对应BeanDefinitionParser用的,它在registerBeanDefinitionParser方法中进行注册:

protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
    this.parsers.put(elementName, parser);
}

而该方法一般在子类的init方法中进行调用,随便打开一个调用:

public class TaskNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        this.registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
        this.registerBeanDefinitionParser("executor", new ExecutorBeanDefinitionParser());
        this.registerBeanDefinitionParser("scheduled-tasks", new ScheduledTasksBeanDefinitionParser());
        this.registerBeanDefinitionParser("scheduler", new SchedulerBeanDefinitionParser());
    }
}

可以发现,我们平时使用的annotation-driven标签原来是这样被注册的~

总结

本文从XmlBeanFactory开始一层层揭开了bean解析的神秘面纱。从XML的解析到bean标签的解析注册,险些跟丢,通盘理解下来之后,不得不赞叹Spring源码的精美。

bean的解析讲完了,下次会带领大家来分析bean是如何被实例化和获取的。



坚持原创技术分享,您的支持将鼓励我继续创作!



分享

评论