Spring源码解析(四)

98 阅读8分钟

ClassPathXmlApplicationContext构造方法中的refresh方法初始化了Spring容器。

public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      prepareRefresh();
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
      prepareBeanFactory(beanFactory);

      try {
         postProcessBeanFactory(beanFactory);
         invokeBeanFactoryPostProcessors(beanFactory);
         registerBeanPostProcessors(beanFactory);
         initMessageSource();
         initApplicationEventMulticaster();
         onRefresh();
         registerListeners();
         finishBeanFactoryInitialization(beanFactory);
         finishRefresh();
      }
      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }
         destroyBeans();
         cancelRefresh(ex);
         throw ex;
      }
      finally {
         resetCommonCaches();
      }
   }
}

prepareRefresh方法做了以下几件事情:

  1. 设置容器启动时间
  2. 设置容器active标志位为true
  3. 设置容器closed标志位为false
  4. 执行initPropertySources方法
  5. 获取Environment对象,校验环境变量值并添加到Environment对象中
  6. 准备监听器和事件的集合对象
protected void prepareRefresh() {
   this.startupDate = System.currentTimeMillis();
   this.closed.set(false);
   this.active.set(true);

   if (logger.isInfoEnabled()) {
      logger.info("Refreshing " + this);
   }

   // 空方法,是扩展点
   initPropertySources();

   // 校验环境中的属性,并且添加到Environment对象中
   // 遍历requiredProperties集合中的属性,如果环境中没有,则抛异常
   getEnvironment().validateRequiredProperties();

   // 准备监听器和事件的集合对象
   this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();
}

Spring的强大之处不仅仅在于它为Java开发者提供了极大便利,而且在于它的开放式架构,使得用户可以拥有最大扩展 Spring 的能力。其中,initPropertySources() 方法就是一个例子。

可以看到 prepareRefresh() 中的 initPropertySources() 方法并没有任何参数,而如果进入initPropertySources() 方法内部,也会发现该方法是空的,没有任何逻辑。

Spring这样设计,就是为了让用户根据自己的需要,可以重写 initPropertySources() 方法,并在方法中进行个性化的属性处理以及设置。

例如:在Environment对象中添加一些属性,或者设置环境中必须存在的属性

public class MyClassPathXmlApplicationContext extends ClassPathXmlApplicationContext {

    public MyClassPathXmlApplicationContext(String... configLocations){
        super(configLocations);
    }

    @Override
    protected void initPropertySources() {
        System.out.println("扩展initPropertySource");
        
        //这里添加了一个name属性到Environment里面,以方便我们在后面用到
        getEnvironment().getSystemProperties().put("name","bobo");
        
        //这里要求Environment中必须包含username属性,如果不包含,则抛出异常
        getEnvironment().setRequiredProperties("username");
    }
}

obtainFreshBeanFactory方法创建BeanFactory对象

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

refreshBeanFactory方法:

  1. 判断环境中是否已经有BeanFactory对象,如果有,删除该对象
  2. 创建新的BeanFactory对象
  3. 创建完成后,设置序列号
  4. customizeBeanFactory方法是为BeanFactory对象的allowBeanDefinitionOverriding和allowCircularReferences属性赋值
  5. 加载BeanDefinition对象

解释一下:

  1. allowBeanDefinitionOverriding属性设置为true表示后发现的Bean定义信息可以覆盖前发现的Bean定义信息
  2. allowCircularReferences属性设置为true表示允许循环依赖
protected final void refreshBeanFactory() throws BeansException {
   if (hasBeanFactory()) {
      destroyBeans();
      closeBeanFactory();
   }
   try {
      // 创建BeanFactory对象的步骤如下:
      // 1.new一个DefaultListableBeanFactory对象
      // 2.为DefaultListableBeanFactory对象的属性赋初始值
      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);
   }
}

loadBeanDefinitions方法解析xml文件

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
   // 创建Bean定义信息解析器
   // 将beanFactory作为入参传入,Bean定义信息会存到beanFactory中
   XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

   // 设置Environment对象
   beanDefinitionReader.setEnvironment(this.getEnvironment());
   // 设置资源加载器
   beanDefinitionReader.setResourceLoader(this);
   // 设置实体解析器
   // beanDefinitionReader解析xml文件,需要知道xml文件的内容格式
   // 所以beanDefinitionReader需要先读取dtd和xsd文件
   // 这两个文件定义了xml文件的内容格式
   // 这是在ResourceEntityResolver构造方法中完成的
   beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
   // 设置beanDefinitionReader的validationMode属性值
   initBeanDefinitionReader(beanDefinitionReader);
   loadBeanDefinitions(beanDefinitionReader);
}

xml文件头标明了xml文件的内容格式,现在基本上都是用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.xsd>

xml文件头中xsi:schemaLocation字段值是xsd文件的网络地址,去这个地址可以获取到解析xml文件需要的xsd文件,但是如果公司不连公网,xml文件就无法解析,项目就无法跑起来吗?当然不会。原因是Spring会去读取本地的xsd文件。

本地的xsd文件路径存储在Spring项目中的spring-beans模块下的resources/META-INF文件夹中的spring.schemas文件中。

入參为XmlBeanDefinitionReader对象的loadBeanDefinitions方法

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

在main函数中,new一个ClassPathXmlApplicationContext对象,传入一个入参,这种方法会使得getConfigResources方法返回null,getConfigLocations方法返回配置文件路径

一般情况下只传一个入参,初学Spring源码,我们点击查看最后一行的loadBeanDefinitions方法

入参为String数组的loadBeanDefinitions方法

public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
    Assert.notNull(locations, "Location array must not be null");
    int counter = 0;
    String[] var3 = locations;
    int var4 = locations.length;

    for(int var5 = 0; var5 < var4; ++var5) {
        String location = var3[var5];
        counter += this.loadBeanDefinitions(location);
    }

    return counter;
}

以上代码的主要功能是遍历locations数组中的元素,调用loadBeanDefinitions方法

入参为String的loadBeanDefinitions方法

public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
    return this.loadBeanDefinitions(location, (Set)null);
}

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
    ResourceLoader resourceLoader = this.getResourceLoader();
    if (resourceLoader == null) {
        throw new BeanDefinitionStoreException("Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
    } else {
        int loadCount;
        if (!(resourceLoader instanceof ResourcePatternResolver)) {
            Resource resource = resourceLoader.getResource(location);
            loadCount = this.loadBeanDefinitions((Resource)resource);
            if (actualResources != null) {
                actualResources.add(resource);
            }

            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
            }

            return loadCount;
        } else {
            try {
                Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location);
                loadCount = this.loadBeanDefinitions(resources);
                if (actualResources != null) {
                    Resource[] var6 = resources;
                    int var7 = resources.length;

                    for(int var8 = 0; var8 < var7; ++var8) {
                        Resource resource = var6[var8];
                        actualResources.add(resource);
                    }
                }

                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
                }

                return loadCount;
            } catch (IOException var10) {
                throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", var10);
            }
        }
    }
}

入参为String的loadBeanDefinitions方法中,又调了loadBeanDefinitons方法,有两个入参,一个是String,另一个是Set集合,元素类型是Resource

在入参为DefaultListableBeanFactory对象的loadBeanDefinitions方法中,已经将ResourceLoader设置为DefaultListableBeanFactory对象,这个对象也是ResourcePatternResolver的子类。所以,以上代码会走到else代码块中

else代码块中,又会调一次loadBeanDefinitions方法

入参为Resource数组的loadBeanDefinitions方法

public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
    Assert.notNull(resources, "Resource array must not be null");
    int counter = 0;
    Resource[] var3 = resources;
    int var4 = resources.length;

    for(int var5 = 0; var5 < var4; ++var5) {
        Resource resource = var3[var5];
        counter += this.loadBeanDefinitions((Resource)resource);
    }

    return counter;
}

以上代码中核心功能是遍历Resource数组,调用loadBeanDefinitions方法

入参为Resource的loadBeanDefinitions方法

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

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

    Set<EncodedResource> currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
    if (currentResources == null) {
        currentResources = new HashSet(4);
        this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }

    if (!((Set)currentResources).add(encodedResource)) {
        throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    } else {
        int var5;
        try {
            InputStream inputStream = encodedResource.getResource().getInputStream();

            try {
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }

                var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            } finally {
                inputStream.close();
            }
        } catch (IOException var15) {
            throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var15);
        } finally {
            ((Set)currentResources).remove(encodedResource);
            if (((Set)currentResources).isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }

        }

        return var5;
    }
}

入参为Resource的loadBeanDefinitions方法中将Resource对象封装成EncodedResource,然后调用loadBeanDefinitions方法

在loadBeanDefinitions方法中,创建了字节码输入流,调用doLoadBeanDefinitions方法,读取配置文件中的内容

doLoadBeanDefinitions方法

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
    try {
        Document doc = this.doLoadDocument(inputSource, resource);
        return this.registerBeanDefinitions(doc, resource);
    } catch (BeanDefinitionStoreException var4) {
        throw var4;
    } catch (SAXParseException var5) {
        throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + var5.getLineNumber() + " in XML document from " + resource + " is invalid", var5);
    } catch (SAXException var6) {
        throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", var6);
    } catch (ParserConfigurationException var7) {
        throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, var7);
    } catch (IOException var8) {
        throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, var8);
    } catch (Throwable var9) {
        throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, var9);
    }
}

在doLoadBeanDefinitions方法中,会调用doLoadDocument方法,将输入流以及Resource对象传入,生成Document对象

Document对象中,包含了xml文件的所有内容信息,将这些信息封装成一个个Node,便于后续的解析

registerBeanDefinitions方法

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    // 创建Document对象解析器
    BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
    // getBeanDefinitionCount方法是返回BeanDefinitionMap中的元素个数
    // 由于现在还没有解析出BeanDefinition,所以个数为0
    int countBefore = this.getRegistry().getBeanDefinitionCount();
    // 解析Document对象
    documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
    return this.getRegistry().getBeanDefinitionCount() - countBefore;
}

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    this.logger.debug("Loading bean definitions");
    // Document对象中包含了xml配置文件的所有内容,并且封装成一个个Node
    // getDocumentElement方法是获取到根Node,并且封装成Element,方便后续解析
    Element root = doc.getDocumentElement();
    this.doRegisterBeanDefinitions(root);
}

doRegisterBeanDefinitions方法

protected void doRegisterBeanDefinitions(Element root) {
    BeanDefinitionParserDelegate parent = this.delegate;
    // 创建了一个BeanDefinitionParserDelegate对象
    // 也是用来解析xml文件内容
    this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
    if (this.delegate.isDefaultNamespace(root)) {
        // 一般这里进不来,除非在xml文件中的beans标签中使用profile属性指定某个配置文件
        String profileSpec = root.getAttribute("profile");
        if (StringUtils.hasText(profileSpec)) {
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
            if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                if (this.logger.isInfoEnabled()) {
                    this.logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource());
                }

                return;
            }
        }
    }

    // 空方法,可以进行扩展
    this.preProcessXml(root);
    // 解析xml文件内容,生成BeanDefinition对象
    this.parseBeanDefinitions(root, this.delegate);
    // 空方法,可以进行扩展
    this.postProcessXml(root);
    this.delegate = parent;
}

if判断代码块一般进不去,除非在xml文件中的beans标签中使用profile属性指定某个配置文件

image.png

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;
                // 有两种方法可以解析节点的内容
                // parseDefaultElement方法是用来解析beans标签中自带的原始标签
                // 例如:bean、alias、import、description等
                // parseCustomElement方法是用来解析需要额外引入其他命名空间约束,并通过前缀引用的标签
                if (delegate.isDefaultNamespace(ele)) {
                    this.parseDefaultElement(ele, delegate);
                } else {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    } else {
        delegate.parseCustomElement(root);
    }
}

xml配置文件中的标签分为两种,一种是默认标签,一种是自定义标签。默认标签不用额外导入其他命名空间约束的标签,例如 <bean> 标签。自定义标签需要额外引入其他命名空间约束,并通过前缀引用的标签,例如<context:property-placeholder/>标签

parseDefaultElement方法用于解析默认标签,parseCustomElement方法用于解析自定义标签

parseDefaultElement方法

parseDefaultElement方法是用来解析默认标签

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    if (delegate.nodeNameEquals(ele, "import")) {
        this.importBeanDefinitionResource(ele);
    } else if (delegate.nodeNameEquals(ele, "alias")) {
        this.processAliasRegistration(ele);
    } else if (delegate.nodeNameEquals(ele, "bean")) {
        // 解析bean标签,bean标签里都是对象信息
        this.processBeanDefinition(ele, delegate);
    } else if (delegate.nodeNameEquals(ele, "beans")) {
        this.doRegisterBeanDefinitions(ele);
    }
}

processBeanDefinition方法

processBeanDefinition方法是来解析bean标签,生成BeanDefinition对象,并且注册到容器中

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    // 生成BeanDefinition对象,并且封装成BeanDefinitionHolder对象
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);

        try {
            // 将生成的BeanDefinition对象注册到容器中
            BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry());
        } catch (BeanDefinitionStoreException var5) {
            this.getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, var5);
        }

        this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
}