目录
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());
}