Spring源码学习笔记(三):BeanFactory的初始化以及BeanDefinition的加载过程

应用上下文初始化简要流程一文中主要简要的描述了Spring的上下文加载的简要流程。文本是对《应用上下文初始化简要流程》的补充,主要简单的分析了BeanFactory的初始化以及BeanDefinition加载的过程.

我们知道,Spring通过refresh操作重建了ApplicaitonContext,在这个过程中同时也构建了默认的BeanFactory以及加载了BeanDefinitionAbstractApplicationContext类在refresh()方法中调用了obtainFreshBeanFactory, 此方法主要负责重建BeanFactory以及加载BeanDefinition。 下面我们对此方法进行简要分析。

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        refreshBeanFactory();
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if (logger.isDebugEnabled()) {
            logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
        }
        return beanFactory;
}

可以看出,此方法的主要逻辑由refreshBeanFactory()方法实现,而refreshBeanFactory()方法的默认实现由AbstractRefreshableApplicationContext来提供,代码如下:

protected final void refreshBeanFactory() throws BeansException {
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);
            loadBeanDefinitions(beanFactory);
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

整个refreshBeanFactory()的逻辑是比较清晰的,首先判断是否已存在BeanFactory,如果已存在则销毁:

  • destroyBeans() 清除Bean引用的缓存
  • closeBeanFactory() 释放已创建的BeanFactory

其次调用createBeanFactory建立默认的BeanFactoryDefaultListableBeanFactory————一个基于Bean Definition对象的成熟的Bean工厂;

当BeanFactory建立好之后,进行一项简单的配置customizeBeanFactory(), 以决定在加载BeanDefinition的过程中是否允许循环引用以及是否允许对已有BeanDefinition进行覆写.

最后,方法loadBeanDefinitions()才是重头戏————BeanDefinition的加载过程。

实现loadBeanDefinitions()的子类有多种,如AbstractXmlApplicationContext类提供了基于XML的加载实现,AnnotationConfigWebApplicationContext类提供了在webapp的场景下基于注解配置的加载实现,XmlWebApplicationContext类提供了在webapp场景下基于xml配置的加载实现,XmlPortletApplicationContext提供了在portalet下基于xml配置的加载实现,GroovyWebApplicationContext类提供了基于groovy脚本配置的加载实现。当然也可以自己实现此方法.

此处,我们主要分析基于xml的加载过程。代码如下:

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // 第一行

        beanDefinitionReader.setEnvironment(this.getEnvironment()); //第二行
        beanDefinitionReader.setResourceLoader(this); //第三行
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // 第四行

        initBeanDefinitionReader(beanDefinitionReader); // 第五行
        loadBeanDefinitions(beanDefinitionReader); // 第六行
}

第一行代码中实例化了一个XmlBeanDefinitionReader对象,此对象用于读取xml的bean定义,实际上它会将对于xml的处理委托给BeanDefinitionDocumentReader接口。这个对象的构造器需要传入BeanDefinitionRegistry对象,因此此处beanFactory作为一个BeanDefinitionRegistry对象被使用;

XmlBeanDefinitionReader的构造器中调用了它的父抽象类AbstractBeanDefinitionReader的构造器,在这个构造器中,分别为XmlBeanDefinitionReader对象的属性resourceLoaderEnvironment赋值; 如果beanFactory没有实现ResourceLoader接口则会创建PathMatchingResourcePatternResolver实例作为resourceLoader; 如果beanFactory没有实现EnvironmentCapable则会创建StandardEnvironment作为environment; 此处beanFctory的类型为DefaultListableBeanFactory,因此不具备以上条件,故均采用创建对象;

PathMatchingResourcePatternResolver类代理了DefaultResourceLoader,对于通过简单的location获取ResourceDefaultResourceLoader实现,其余复杂location加载Resource由其自己实现;此处采用的设计模式为静态代理

第二行代码为beanDefinitionReaderenvironment赋值,值为StandardEnvironment实例(非web应用的环境)

第三行代码为beanDefinitionReaderresourceLoader赋值,值为当前上下文(当前对象的父类继承了DefaultResourceLoader)

第四行代码为beanDefinitionReaderentityResolver赋值,值为ResourceEntityResolver的实例.此类作为EntityResolver的实现主要是通过ResourceLoader来尝试解析实体引用; 此类又集成了DelegatingEntityResolver所以也可以查找DTD和XSD。

第五行代码initBeanDefinitionReader(beanDefinitionReader)主要是对beanDefinitionReader进行初始化配置,默认是空实现,主要是用于子类覆盖此方法实现自定义XmlBeanDefinitionReader的初始化配置,例如打开或者关闭XML验证,配置不同的XmlBeanDefinitionParser实现

第六行代码loadBeanDefinitions(beanDefinitionReader)就是BeanDefinition的主要加载过程了.它使用指定的XmlBeanDefinitionReader进行加载BeanDefinition. 这个方法仅负责加载或者注册BeanDefinition,而生命周期是通过bean工厂的refreshBeanFactory方法管理. loadBeanDefinitions代码如下:

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        Resource[] configResources = getConfigResources(); // 1
        if (configResources != null) {
            reader.loadBeanDefinitions(configResources); // 2
        }
        String[] configLocations = getConfigLocations(); // 3
        if (configLocations != null) {
            reader.loadBeanDefinitions(configLocations); // 4
        }
    }

我们可以看出,对于BeanDefinition的加载最终是使用XmlBeanDefinitionReader的实例进行加载的,此处只是对其指定了加载的资源或者资源的位置。
1处获取是否有配置好的Resource,如果有直接交由reader进行加载(默认实现中是没有配置好的Resource,但可以覆写getConfigResources()进行配置);
3处获取配置的XML位置,如果没有配置xml的位置,则去默认的地方获取xml;当获取到xml路径的字符串后,则交由reader进行解析加载(4处),代码如下:

public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
        Assert.notNull(locations, "Location array must not be null");
        int counter = 0;
        for (String location : locations) {
            counter += loadBeanDefinitions(location); // 1
        }
        return counter;
    }

此方法位于AbstractBeanDefinitionReader类中,主要针对字符串路径进行解析,最终得到Resource对象,然后将Resource交由XmlBeanDefinitionReader进行处理,所以针对xml的资源加载的最终位置还是位于XmlBeanDefinitionReader中;

我们来看下在Spring中如何将xml位置的字符串转换为Resource的,代码如下:

public Resource[] getResources(String locationPattern) throws IOException {
        Assert.notNull(locationPattern, "Location pattern must not be null");
        if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
            if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
                return findPathMatchingResources(locationPattern);
            }else {
                return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
            }
        }
        else {
            int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :   locationPattern.indexOf(":") + 1);
            if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
                return findPathMatchingResources(locationPattern);
            }
            else {
                return new Resource[] {getResourceLoader().getResource(locationPattern)};
            }
        }
    }
  • 配置的xml位置字符串是以classpath*:开头,则
    • 位置字符串中可能包含多个xml位置路径,如classpath*:dir/**等,则调用findPathMatchingResources方法查找符合配置的表达式的资源
    • 位置字符串中是一个固定的xml路径,如classpath*:/org/abc/, 则调用findAllClassPathResources加载此位置下的所有文件.
  • 配置的xml位置字符串不以classpath*:开头,去掉资源访问协议,如果协议是war:,则需要截取*/后边的内容,否则直接去:后边的内容, 根据其:
    • 判断是否符合通配符配置,如果符合则交由findPathMatchingResources加载资源
    • 不符合通配符则直接加载资源,如/applition.xml

通过以上步骤,一个xml位置的字符串就会形成Spring中统一的资源抽象Resource, 形成Resource的一个或者多个实例之后,则对其进行解析与加载bean definition. 对Resource进行解析和加载是通过接口BeanDefinitionReader定义的,对xml文件加载的实现则由其实现类XmlBeanDefinitionReader实现. 关键代码:

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (logger.isInfoEnabled()) {
            logger.info("Loading XML bean definitions from " + encodedResource.getResource());
        }

        Set currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        if (!currentResources.add(encodedResource)) {  // 标注1
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        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()); // 标注2
            }
            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();
            }
        }
    }

值得注意的是,在解析Resource之前,Spring会将此Resource存储于当前线程的局部变量ThreadLocal> resourcesCurrentlyBeingLoaded中;在解析之后会将Resource从resourcesCurrentlyBeingLoaded移除。 但这个操作的目的是什么呢?我们从两方面来分析:

    1. 利用Set结构防止重复解析,注意看标注1处的代码,我们知道Set内元素唯一,如果已经将同一个Resource多次添加至Set中时,除了第一次之外其余都不会添加成功的;
    1. 利用ThreadLocal确保线程安全。 虽然第一步通过Set防止了重复解析,但前提是单线程情况下; 如果此方法被多线程调用时,那么还是会有几率出现一个Resource被多次解析; 传统的解决方式是使用synchronized进行同步,但加锁会导致解析效率低下,所以此处将其放置于ThreadLocal对象中,确保每个线程只能访问自己的Set,从而既保证了性能,又解决了线程不安全的问题.

在确保单个线程内Resource不会被重复消费的情况下,Spring开始了对Resource的解析,标志2处代码实现:

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

我们可以看到通过doLoadDocument方法将其解析为Document对象,然后通过registerBeanDefinitions方法对Document对象进行提取与BeanDefinition的注册.

这里的Document是符合w3c定义的标准xml文档; 我们知道xml解析一般有DOM方式(document)、SAX方式(Simple API for XML)以及StAX(Streaming API for xml)方式;而此处的Resource不会包含太大量的信息,所以采用了DOM方式,即一次将整个XML都加载到内存中进行解析.

当我们得到Document之后是如何对其进行解析和注册的呢?代码:

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

这个方法的作用就是对给予的Document中的BeanDefinition进行注册。

第一行代码创建了一个BeanDefinitionDocumentReader对象,此对象为BeanDefinition注册到容器的一个SPI(Service Provider Interface), 它具有一个默认的实现DefaultBeanDefinitionDocumentReader; 此外这里还有一个比较有趣的点,产生此对象的方式不是直接通过new获取的,而是通过反射获取此对象的实例:

BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass))

这样做的主要的目的还是为了方便后续扩展,也为自定义注册规则成为可能.

第二行代码是获取对此Document解析前的已注册的beandefinition的数量

第三行代码是对此Document进行解析和注册的过程,具体的实现位于DefaultBeanDefinitionDocumentReader类中,代码:

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

    protected void doRegisterBeanDefinitions(Element root) {
        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = createDelegate(getReaderContext(), root, parent);

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

        preProcessXml(root);
        parseBeanDefinitions(root, this.delegate); // 标注1
        postProcessXml(root);

        this.delegate = parent;
    }

方法doRegisterBeanDefinitions会将root中的每个bean definition都进行注册,对于root的解析的逻辑委托BeanDefinitionParserDelegate对象实现. 解析的root的过程从标注1处的方法开始:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        if (delegate.isDefaultNamespace(root)) { // 标注1
            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); // 标注2
                    }
                    else {
                        delegate.parseCustomElement(ele); // 标注3
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }

标注1判断root元素的namespace是否为http://www.springframework.org/schema/beans, 如果是,则获取root的的子节点,如果子节点的namespace为http://www.springframework.org/schema/beans, 则交由方法parseDefaultElement解析,如果不是则交由委托类解析delegate.parseCustomElement(比如,以及自定义标签等);

命名空间http://www.springframework.org/schema/beans主要包含了4类节点, , 如果root元素或者其子元素为此4类节点,则注解进入标注2的方法,进行解析和注册,代码如下:

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { // 标注1
            importBeanDefinitionResource(ele);
        }
        else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { // 标注2
            processAliasRegistration(ele);
        }
        else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { // 标识3
            processBeanDefinition(ele, delegate);
        }
        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // 标注4
            // recurse
            doRegisterBeanDefinitions(ele);
        }
    }

根据代码我们可知:

  • import标签,通过importBeanDefinitionResource方法进行解析,大致的逻辑是通过resource属性取得配置文件的位置,然后将其转换为Resource,交由XmlBeanDefinitionReader来解析.importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
  • alias标签,通过processAliasRegistration方法进行解析,在进行进行是否可重写校验和循环引用校验后,将alias标签的namealias属性添加到上下文的aliasMap中;
  • beans标签,进行迭代调用doRegisterBeanDefinitions方法
  • bean标签,通过processBeanDefinition进行处理和注册,代码如下:
  protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); //标注1
        if (bdHolder != null) {
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); // 标注2
            }
            catch (BeanDefinitionStoreException ex) {
                getReaderContext().error("Failed to register bean definition with name '" +
                        bdHolder.getBeanName() + "'", ele, ex);
            }
            getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); // 标注3
        }
    }

可以看出,首先通过delegate的方法parseBeanDefinitionElement将element解析为BeanDefinitionHolder对象,然后对此对象进行必须得装配后,调用标注2处的方法将其注册到上下文中;

那么BeanDefinitionHolder到底是一个什么对象呢?BeanDefinitionHolder对象通过namealias持有BeanDefinition,最终注册的时候会将其持有的BeanDefinition注册到上下文中;

下面主要看下BeanDefinitionHolder持有的BeanDefinition的产生过程:

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(); // 提取class属性
        }

        try {
            String parent = null;
            if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
                parent = ele.getAttribute(PARENT_ATTRIBUTE); // 提起parent属性
            }
            // 根据class属性和parent属性创建BeanDefinition, 
            // 首先通过类加载器寻找class类并将其设置于beanClass属性
            // 如果类加载器暂时无法发现此类,则将class设置于beanClassName属性上
            AbstractBeanDefinition bd = createBeanDefinition(className, parent); 

      // 将元素中定义的bean的属性提取并设置到AbstractBeanDefinition中,
      // 提取的属性有scope abstract lazy-init autowire denpendency-check
      // depends-on autowire-candidate parimary  init-method  destory-method 
      // factory-method factory-bean
            parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); 

            // 提取desciption标签内容
            bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

      // 解析meta标签中配置的k-v数据
            parseMetaElements(ele, bd);

            // 解析配置的lookup-method标签,主要用于无参方法覆盖
            parseLookupOverrideSubElements(ele, bd.getMethodOverrides());

            // 解析配置的replaced-method标签,主要用于有参方法替换
            parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

      // 解析配置的construnctor-arg标签,主要用于声明有参构造函数的参数
            parseConstructorArgElements(ele, bd);

            // 解析配置的property标签
            parsePropertyElements(ele, bd);

            // 解析配置的qualifier标签
            parseQualifierElements(ele, bd);

      // 设置resource
            bd.setResource(this.readerContext.getResource());

            // 用于设置 允许工具控制元数据如何附加到bean definition的策略
            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;
    }

当生成AbstractBeanDefinition后,还得给起一个唯一的名字,BeanDefinitionReaderUtils.generateBeanName代码如下:

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

现在名字有了,BeanDefinition也具备了,就剩下将其注册到应用上下文了。

  // BeanDefinitionReaderUtils
    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()); // 标注1

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

调用标注1的地方进行注册,实际上是由DefaultListableBeanFactoryregisterBeanDefinition来实现其逻辑的,在此方法最终会做以下事情:

  • BeanDefinition缓存至beanDefinitionMap
  • 将beanName缓存至beanDefinitionNames

至此,xml中定义的BeanDefinition元数据被注册至应用上下文中.

你可能感兴趣的:(Spring)