天天看点

Spring源码学习(一):Bean的加载和初步解析1.一个示例2.定位和加载配置文件3 容器的刷新与Bean的加载

目录

1.一个示例

2.定位和加载配置文件

2.1 沿继承链向上调用父类构造函数

2.2 设置配置文件路径

3 容器的刷新与Bean的加载

3.1 prepareRefresh

3.2 obtainFreshBeanFactory

1.一个示例

下面是一个最基本的Spring示例:

配置文件config.xml:

<?xml version="1.0" encoding="UTF-8"?>   
<beans>   
    <bean id="hello" class="Hello"/>
</beans>
           

主函数,BeanFactory也可以写为ApplicationContext,ApplicationContext实际上派生自BeanFactory:

public static void main(String[] args) {
    BeanFactory beanFactory = new ClassPathXmlApplicationContext("config.xml");
    Hello hello = beanFactory.getBean("hello");
    hello.sayHello();
    beanFactory.close();
}
           

Hello类:

public class Hello {
    public void sayHello() {
        System.out.println("Hello World!");
    }
}
           

显然,本例中,主函数主要做了三件事:

  • 定位和加载配置文件config.xml
  • 读取和加载配置文件中定义的Bean
  • 实例化Bean并调用其方法

首先来看下第一步。

2.定位和加载配置文件

首先看下ClassPathXmlApplicationContext构造函数源码:

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
        super(parent);
        this.setConfigLocations(configLocations);
        if (refresh) {
            this.refresh();
        }
    }
           

显然做了三件事:沿继承链向上调用父类构造函数、设置配置文件路径、刷新容器。

2.1 沿继承链向上调用父类构造函数

最终调用了AbstractApplicationContext的构造函数,该构造函数对一些成员变量进行了初始化,并将传入的parent参数设置为自身的父容器。

public AbstractApplicationContext() {
        this.logger = LogFactory.getLog(this.getClass());
        this.id = ObjectUtils.identityToString(this);
        this.displayName = ObjectUtils.identityToString(this);
        this.beanFactoryPostProcessors = new ArrayList();
        this.active = new AtomicBoolean();
        this.closed = new AtomicBoolean();
        this.startupShutdownMonitor = new Object();
        this.applicationListeners = new LinkedHashSet();
        this.resourcePatternResolver = this.getResourcePatternResolver();
    }

    public AbstractApplicationContext(@Nullable ApplicationContext parent) {
        this();
        this.setParent(parent);
    }
           

resourcePatternResolver变量初始化为PathMatchingResourcePatternResolver,支持Ant风格路径的解析。

2.2 设置配置文件路径

该方法主要工作是解析传入的路径,并复制到类成员数组中。有时为了方便,会在路径中使用占位符,例如:"${path}:config.xml",对于这种路径,显然无法直接读取。

路径的解析需要依赖当前的容器环境Envrionment,假如不存在,会调用createEnvironment创建一个StandardEnvironment实例。

不过StandardEnvironment的构造函数为空,实际还是调用了其抽象基类AbstractEnvironment的构造函数,对PropertyResolver进行初始化:

public AbstractEnvironment() {
        this.propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);
        this.customizePropertySources(this.propertySources); //空方法,在StandardEnvironment中实现
    }
	
    //该方法位于StandardEnvironment
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new MapPropertySource("systemProperties", this.getSystemProperties()));
        propertySources.addLast(new SystemEnvironmentPropertySource("systemEnvironment", this.getSystemEnvironment()));
    }
           

getSystemProperties和getSystemEnvironment类似,都是先尝试获取所有信息,如果遇到AccessControlException,则尝试获取单个信息,如果仍然失败,则返回null。然后封装为PropertySource对象,存入MutablePropertySources(多了一个s)对象中。

接着,借助刚刚初始化的PropertyResolver,调用resolveRequiredPlaceholders方法进行解析。解析到过程实际就是从PropertySource中获取属性,尝试进行字符串替换。

public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
    if (this.strictHelper == null) {
        this.strictHelper = createPlaceholderHelper(false);
    }
    return doResolvePlaceholders(text, this.strictHelper);
}
private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
    return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
        this.valueSeparator, ignoreUnresolvablePlaceholders);
}
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
    return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
        @Override
        public String resolvePlaceholder(String placeholderName) {
            return getPropertyAsRawString(placeholderName);
        }
    });
}
           

placeholderPrefix、placeholderSuffix、valueSeparator实际就是"${"、"}"、":"(不含引号)。

到目前为止,才刚刚完成路径的解析,还没有正式开始读取XML配置

3 容器的刷新与Bean的加载

由于传入了新的配置文件,容器的环境肯定发生了改变,此时就需要对容器进行一次刷新(对于新启动的容器来说,刷新其实就是初始化)。

由于refresh方法过于重要,因此单独成为一节。它的源码如下(日志记录部分省略):

public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);
            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }
           

3.1 prepareRefresh

在刷新开始之前,首先需要进行一些准备,比如说设置容器状态、检查启动必要的属性是否齐全等:

protected void prepareRefresh() {
        this.startupDate = System.currentTimeMillis();
        this.closed.set(false);
        this.active.set(true);
        this.initPropertySources();
        this.getEnvironment().validateRequiredProperties();
        this.earlyApplicationEvents = new LinkedHashSet();
    }
           

initPropertySources是一个protected空方法,可以由开发者进行扩展。

3.2 obtainFreshBeanFactory

环境准备完毕后,就会开始刷新BeanFactory,该过程分为两步:刷新、返回。

首先是刷新过程,如果是一个已经存在的容器刷新,需要先关闭已有的容器,之后创建一个DefaultListableBeanFactory对象,由它进行Bean的加载和注册:

//位于AbstractRefreshableApplicationContext
    protected final void refreshBeanFactory() throws BeansException {
        if (this.hasBeanFactory()) {
            this.destroyBeans();
            this.closeBeanFactory();
        }

        try {
            DefaultListableBeanFactory beanFactory = this.createBeanFactory();
            beanFactory.setSerializationId(this.getId());
            this.customizeBeanFactory(beanFactory);
            this.loadBeanDefinitions(beanFactory);
            synchronized(this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        } catch (IOException var5) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);
        }
    }
           

DefaultListableBeanFactory是Bean加载的核心,所有的容器实现都是基于它的。

customizeBeanFactory可以配置BeanFactory是否允许覆盖或循环引用。

loadBeanDefinitions是一个抽象方法,由子类实现。这是因为Bean的定义方式有两种:XML配置、注解配置,两种方式解析方法肯定不同,也要有不同的实现。XML版本的实现位于AbstractXmlApplicationContext类:

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));
        this.initBeanDefinitionReader(beanDefinitionReader);
        this.loadBeanDefinitions(beanDefinitionReader);
    }
           

此处创建了一个XmlBeanDefinitionReader对象,并为它配置了Environment、ResourceLoader(读取配置文件路径并加载为Resource对象)、EntityResolver(来自SAX库,用于解析XML中的DTD/XSD)。

loadBeanDefinitions(beanDefinitionReader)会优先读取Resource对象进行加载,ClassPathXmlApplicationContext提供的是ClasspathResource实现。然后就会借助XmlBeanDefinitionReader进行BeanDefinition的读取(以Resource版本为例):

public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
        Assert.notNull(resources, "Resource array must not be null");
        int count = 0;
        Resource[] var3 = resources;
        int var4 = resources.length;
        for(int var5 = 0; var5 < var4; ++var5) {
            Resource resource = var3[var5];
            count += this.loadBeanDefinitions((Resource)resource);
        }

        return count;
    }
           

XmlBeanDefinitionReader首先将传入的Resource转换为EncodedResource,然后获取输入流,构造InputSource,并使用SAX将InputSource解析为Document对象。在解析之前,会先对XML进行校验:

protected int getValidationModeForResource(Resource resource) {
        int validationModeToUse = this.getValidationMode();
        if (validationModeToUse != 1) {
            return validationModeToUse;
        } else {
            int detectedMode = this.detectValidationMode(resource);
            return detectedMode != 1 ? detectedMode : 3;
        }
    }
           

XML有两种验证模式:XSD和DTD,假如指定了验证模式(即validationModeToUse不为1),则按照指定的来,否则读取XML的doctype标签自动进行检测。这时候就要用上上面设置到EntityResolver了,如果是XSD模式,就要寻找META-INF/spring.schemas文件来进行验证:

public InputSource resolveEntity(String publicId, @Nullable String systemId) throws SAXException, IOException {
        if (systemId != null) {
            if (systemId.endsWith(".dtd")) {
                return this.dtdResolver.resolveEntity(publicId, systemId);
            }

            if (systemId.endsWith(".xsd")) {
                return this.schemaResolver.resolveEntity(publicId, systemId);
            }
        }

        return null;
    }
           

假如使用的是String版本的方法,会使用构造ApplicationContext时创建的PathMatchingResourcePatternResolver来进行路径的解析和加载。

到这一步,XML已经转换为Document对象,接下来就是将Bean的信息提取出来,组装为BeanDefinition并注册。

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

documentReader实际类型为DefaultBeanDefinitionDocumentReader,其主要功能就是提取DOM树的RootElement,用于Bean注册。在解析RootElement前后,Spring还预留了扩展点,供开发者二次开发:

protected void doRegisterBeanDefinitions(Element root) {
        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
        if (this.delegate.isDefaultNamespace(root)) {
            String profileSpec = root.getAttribute("profile");
            if (StringUtils.hasText(profileSpec)) {
                String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
                if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                    if (this.logger.isDebugEnabled()) {
                        this.logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource());
                    }

                    return;
                }
            }
        }
        this.preProcessXml(root); //解析前处理的扩展点
        this.parseBeanDefinitions(root, this.delegate);
        this.postProcessXml(root); //解析后处理的扩展点
        this.delegate = parent;
    }
           

在正式解析前,程序会首先判断命名空间是Spring标准的,还是自定义的。上面也提到,Spring提供了Schema功能,即允许用户自定义标签,并提供XSD进行验证,这里的namespace就可以帮助Spring识别配置文件使用了哪个依赖包的扩展,其还有一个用处,在接下来正式解析时会用到。

如果是标准命名空间,则从RootElement中解析profile属性的值。Spring的Profile特性是从3.1版本加入的,可以让线上环境和测试环境使用不同的配置,启动容器时,只要修改一下配置,就能在多个环境无缝切换。一个profile配置类似:

<beans profile="develop">  
    <bean id="hello" class="Hello"> 
</beans>  
<beans profile="production">  
    <bean id="bye" class="Bye">  
</beans>
           

在主函数中加上一句:context.getEnvironment().setActiveProfiles("develop"); 就可以启用开发配置了

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        if (delegate.isDefaultNamespace(root)) {
            NodeList nl = root.getChildNodes();

            for(int i = 0; i < nl.getLength(); ++i) {
                Node node = nl.item(i);
                if (node instanceof Element) {
                    Element ele = (Element)node;
                    if (delegate.isDefaultNamespace(ele)) {
                        this.parseDefaultElement(ele, delegate);
                    } else {
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        } else {
            delegate.parseCustomElement(root);
        }

    }
           

这里也是根据命名空间,调用不同的解析方法。对于标准命名空间的元素,直接调用内置方法处理,处理的标签有四种:import、alias、bean、beans:

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")) {
            this.processBeanDefinition(ele, delegate);
        } else if (delegate.nodeNameEquals(ele, "beans")) {
            this.doRegisterBeanDefinitions(ele);
        }

    }
           

对于自定义的命名空间,会找到自定义的NamespaceHandler进行解析:

public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
        String namespaceUri = this.getNamespaceURI(ele);
        if (namespaceUri == null) {
            return null;
        } else {
            NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
            if (handler == null) {
                this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
                return null;
            } else {
                return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
            }
        }
    }
           

举个例子:

阿里的Dubbo就利用了Spring Schema的特性来进行自定义标签的扩展,在一个Dubbo配置文件中,可以看到它的namespace是dubbo,于是在beans标签中可以定位到其命名空间为“http://code.alibabatech.com/schema/dubbo”:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
        http://code.alibabatech.com/schema/dubbo
        http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
    <dubbo:application name="dubbo-server"/>
    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>
    <dubbo:protocol name="dubbo" port="20880"/>
    <dubbo:service inter ref="helloService"/>
    <bean id="helloService" class="HelloServiceImpl"/>
</beans>
           

通过这个uri,可以找到它的包是com.alibaba:dubbo,在包的META-INF下,有一个spring.handlers文件,内容如下:

http\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
           

在IDEA中,我们可以直接Ctrl+单击左键进入类中,其init方法注册了各tag的解析器,调用NamespaceHandler的parse方法时,会从解析器列表中根据tag名找到对应的解析器进行实际解析。

public void init() {
        this.registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        this.registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
        this.registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
        this.registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        this.registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
        this.registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
        this.registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        this.registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        this.registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        this.registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
    }