三、加载BeanDefinition

加载BeanDefinition
1
2
3
4
5
6
7
8
//获取资源
ClassPathResource resource = new ClassPathResource("bean.xml");
//获取 BeanFactory
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
//根据新建的 BeanFactory 创建一个 BeanDefinitionReader 对象,该 Reader 对象为资源的解析器
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
//装载资源
reader.loadBeanDefinitions(resource);

这段代码是 Spring 中编程式使用 IoC 容器,IoC 容器的使用过程如下:

资源定位、装载、注册。

资源定位:IOC容器定位资源上一片《Spring统一资源加载策略》。

装载:装载就是 BeanDefinition 的载入,BeanDefinitionReader 读取、解析 Resource 资源,也就是将用户定义的 Bean 表示成 IoC 容器的内部数据结构:BeanDefinition

  • 在 IoC 容器内部维护着一个 BeanDefinition Map 的数据结构。
  • 在配置文件中每一个都对应着一个 BeanDefinition 对象。

注册:向 IoC 容器注册在第二步解析好的 BeanDefinition,其实就是将BeanDefinition注入到一个HashMap容器中,这个过程是通过 BeanDefinitionRegistry 接口来实现的,IoC 容器就是通过这个 HashMap 来维护这些 BeanDefinition 的。

  • 在这里需要注意的一点是这个过程并没有完成依赖注入(Bean 创建),Bean 创建是发生在应用第一次调用 getBean(...) 方法,向容器索要 Bean 时。
  • 当然我们可以通过设置预处理,即对某个 Bean 设置 lazyinit = false 属性,那么这个 Bean 的依赖注入就会在容器初始化的时候完成。

loadBeanDefinitions

装载,通过reader.loadBeanDefinitions(resource);

1
2
3
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
  • 从指定的 xml 文件加载 BeanDefinition ,这里会先对 Resource 资源封装成 org.springframework.core.io.support.EncodedResource 对象。这里为什么需要将 Resource 封装成 EncodedResource 呢?主要是为了对 Resource 进行编码,保证内容读取的正确性。

  • 然后,再调用 #loadBeanDefinitions(EncodedResource encodedResource) 方法,执行真正的逻辑实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded =
new NamedThreadLocal<Set<EncodedResource>>("XML bean definition resources currently being loaded"){
@Override
protected Set<EncodedResource> initialValue() {
return new HashSet<>(4);
}
};

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

// <1> 获取已经加载过的资源
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

if (!currentResources.add(encodedResource)) {// 将当前资源加入记录中。如果已存在,抛出异常
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}

// <2> 从 EncodedResource 获取封装的 Resource ,并从 Resource 中获取其中的 InputStream
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 核心逻辑部分,执行加载 BeanDefinition
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
// 从缓存中剔除该资源 <3>
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}

<1> 处,通过 resourcesCurrentlyBeingLoaded.get() 代码,来获取已经加载过的资源,然后将 encodedResource 加入其中,如果 resourcesCurrentlyBeingLoaded 中已经存在该资源,则抛出 BeanDefinitionStoreException 异常。

  • 为什么需要这么做呢?答案在 "Detected cyclic loading" ,避免一个 EncodedResource 在加载时,还没加载完成,又加载自身,从而导致死循环
  • 也因此,在 <3> 处,当一个 EncodedResource 加载完成后,需要从缓存中剔除。

<2> 处理,从 encodedResource 获取封装的 Resource 资源,并从 Resource 中获取相应的 InputStream ,然后将 InputStream 封装为 InputSource ,最后调用 #doLoadBeanDefinitions(InputSource inputSource, Resource resource) 方法,执行加载 Bean Definition 的真正逻辑。

doLoadBeanDefinitions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// <1> 获取 XML Document 实例
Document doc = doLoadDocument(inputSource, resource);
// <2> 根据 Document 实例,注册 Bean 信息
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
  • <1> 处,调用 #doLoadDocument(InputSource inputSource, Resource resource) 方法,根据 xml 文件,获取 Document 实例。
  • <2> 处,调用 #registerBeanDefinitions(Document doc, Resource resource) 方法,根据获取的 Document 实例,注册 Bean 信息。
doLoadDocument
1
2
3
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,getValidationModeForResource(resource), isNamespaceAware());
}

调用 #getValidationModeForResource(Resource resource) 方法,获取指定资源(xml)的验证模式看下一篇

调用 DocumentLoader#loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) 方法,获取 XML Document 实例。

registerBeanDefinitions

看注册BeanDefinitions


三、加载BeanDefinition
http://www.muzili.ren/2022/06/11/加载BeanDefinition/
作者
jievhaha
发布于
2022年6月11日
许可协议