接着上一篇文章,本篇来接着谈谈 Spring 容器的创建及 bean 定义的解析。Debug 使用的代码和上一篇文章提供的保持一致
本篇文章由于代码调用流程较长,所以先给大家呈现一张调用流程图,方便大家更容易理解调用层次,废话不多说了,先上图一睹为快。
创建BeanFactory及注册BeanDefinition流程
直接来看下
obtainFreshBeanFactory
源码实现
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
// Step1:创建BeanFactory并注册BeanDefinition定义
// 通过这个方法可以看出,所有实现是在这个方法里面完成的
refreshBeanFactory();
// Step2:获取创建好的BeanFactory对象,并返回
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
refreshBeanFactory
实现其实调用了其子类AbstractRefreshableApplicationContext.refreshBeanFactory()
方法。
protected final void refreshBeanFactory() throws BeansException {
// Step1:判断BeanFactory是否已经创建好,一般情况下第一次初始化Spring容器的时候if是不成立的,因为在此之前没有地方可以创建出BeanFactory对象
if (hasBeanFactory()) {
// 如果已经创建好,先将创建好的bean实例,相关缓存,BeanDefinition定义全部清空
// Spring源码中大量运用了缓存,来提高Spring容器初始化的速度
destroyBeans();
// 然后在将BeanFactory置为null
closeBeanFactory();
}
try {
// Step2:创建一个DefaultListableBeanFactory对象,该对象用于存储Bean实例、相关缓存、BeanDefinition定义等等
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
// Step3:设置是否允许BeanDefinition是否可以被覆盖
// 设置循环引用
customizeBeanFactory(beanFactory);
// Step4:加载BeanDefinition定义,并将其注册到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()
实现由AbstractRefreshableApplicationContext
子类AbstractXmlApplicationContext.loadBeanDefinitions()
方法实现的。
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
// Step1:创建一个XmlBeanDefinitionReader对象,用于读取xml文档
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
// Step2:设置环境变量
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
// Step3:初始化BeanDefinitionReader,设置是否启用xml文档校验,默认情况下是需要校验的
// 说白了就是是否要校验xml文档的合法性,避免错误解析
initBeanDefinitionReader(beanDefinitionReader);
// Step4:加载beanDefinitions
loadBeanDefinitions(beanDefinitionReader);
}
初始化
XmlBeanDefinitionReader
对象完成后,开始读取配置文件资源。
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
// Step1:首先读取ClassPathXmlApplicationContext里面存放点的Resource资源
Resource[] configResources = getConfigResources();
if (configResources != null) {
// Step1.1:如果Resource资源不为空,则开始加载Resource资源
reader.loadBeanDefinitions(configResources);
}
// Step2:读取AbstractRefreshableConfigApplicationContext指定配置文件资源
String[] configLocations = getConfigLocations();
if (configLocations != null) {
// Step2.1:如果Location资源不为空,则开始加载Location资源
reader.loadBeanDefinitions(configLocations);
}
}
获取到配置文件路径后,开始遍历解析每个配置文件,不管是第一种资源还是第二种资源,最后都统一调用了
XmlBeanDefinitionReader
类的父类AbstractBeanDefinitionReader的loadBeanDefinitions()
方法。
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
// Step1:获取资源读取器
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
// Step2:如果当前的资源读取器实现了ResourcePatternResolver接口
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
// Step2.1:获取resource资源
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
// Step2.2:加载所有指定的resource资源里面定义的BeanDefinitions
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// Can only load single resources by absolute URL.
// Step3:如果没有实现ResourcePatternResolver接口,则根据当前的resourceLoader获取Resource资源
Resource resource = resourceLoader.getResource(location);
// Step3.1:加载指定的Resource资源定义的BeanDefinitions
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
上面这段代码主要作用是使用不同的
BeanDefinitionReader
读取器读取不同类型的配置文件。拿我们提供的测试例子,最后其实调用了XmlBeanDefinitioneader.loadBeanDefinitions()
方法。在这个方法里面我们看到一个比较特别的方法,叫做doLoadBeanDefinitions()
,其实真正加载BeanDefinition
是在这个方法里面实现的。
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());
}
// Step1:获取当前的Resource对象,刚开始肯定是没有的
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
// Step1.1:将当前Resource设置进去
currentResources = new HashSet<>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
// Step2:只有在第一次添加的时候才会返回true,也就意味着如果当前resource已经被设置过了,则出现了重复加载,直接抛出异常
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
// Step3:根据resource对象获取对应的InputStream
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// Step4:拿到inputSource对象,开始正式读取配置文件,并创建配置文件中定义的BeanDefinition
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
// Setp5:关闭输入流
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();
}
}
}
拿到了
InputSource
之后,来看看配置文件到底是如何被解析的。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// Step1:加载Document对象,其实就是把xml文件解析成一个文档对象返回
Document doc = doLoadDocument(inputSource, resource);
// Step2:对Document对象遍历,读取不同的标签名,创建对应的BeanDefinition,并将创建出来的BeanDefinition注册到BeanFactory中去
return registerBeanDefinitions(doc, resource);
}
// 此处为了方便阅读,将各种异常代码用.....代替
......
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
先来看看
Document
对象是怎么创建出来的。
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
// Step1:创建Document工厂,这里面其实就是设置配置文件的文件头
// 比如applicationContext.xml文件里面最开始,beans标签里面指定的 .xsd。了解即可
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
// Step2:利用Document工厂,创建对应的DocumentBuilder。
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
// Step3:利用builder去解析输入流,从而构建出Document对象
return builder.parse(inputSource);
}
// 这个方法就是文档的一些解析处理,了解一下
public Document parse(InputSource is) throws SAXException, IOException {
if (is == null) {
throw new IllegalArgumentException(
DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN,
"jaxp-null-input-source", null));
}
if (fSchemaValidator != null) {
if (fSchemaValidationManager != null) {
fSchemaValidationManager.reset();
fUnparsedEntityHandler.reset();
}
resetSchemaValidator();
}
domParser.parse(is);
Document doc = domParser.getDocument();
domParser.dropDocumentReferences();
return doc;
}
到现在,已经将 xml 文件解析成
Document
对象了,接下来看看根据Document
对象如何创建BeanDefinition
对象并注册到BeanFactory
里面。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
// 这里我们只关注如何把Document对象注册到BeanFactory里面
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
protected void doRegisterBeanDefinitions(Element root) {
// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
// Step1:以beans开始,是否可以取到 “profile”标签,我们没有在xml文件里面指定,所以这个地方可以忽略
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;
}
}
}
// Step2:解析前工作,默认没有实现,留出来扩展
preProcessXml(root);
// Step3:开始解析Document
parseBeanDefinitions(root, this.delegate);
// Step4:解析后工作,默认没有实现,留出来扩展
postProcessXml(root);
this.delegate = parent;
}
重点来了,我们在 xml 文件里面配置的
Bean
标签就是在这个里面被转化成一个个BeanDefinition
对象的。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// Step1:如果是默认的命名空间,使用默认的解析逻辑
if (delegate.isDefaultNamespace(root)) {
// Step1.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;
// Step1.2:如果子节点是 “import”、“alias”、“bean”、“beans”等等的
// 则走默认的标签解析处理逻辑
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
// Step1.3:如果不是上面指定的这些标签的话,则走特殊的标签解析逻辑
delegate.parseCustomElement(ele);
}
}
}
}
// Step2:不是默认的命名空间,则走自己的解析逻辑
else {
delegate.parseCustomElement(root);
}
}
总结
从本篇文章不难看出,obtainFreshBeanFactory 就是创建了一个 BeanFactory 对象,然后把 xml 配置文件解析成 Document 对象,然后对 Document 对象遍历,创建出相应的 BeanDefinition 定义,并把 BeanDefinition 对象注册到 BeanFactory 里面。
到这里我们拿到了 xml 配置的 bean 标签。限于文章篇幅过长,我在下一篇文章中来继续解释,bean 标签是如何被解析的,比如 id,class,scope 等等属性。
欢迎关注我,共同学习