Spring - IOC 容器初始化

Love The Way You Lie 2021-08-29 13:29 566阅读 0赞

IOC初始化总结

    1. 简介
    1. Resource 定位
    1. BeanDefinition 的载入和解析
    • 3.1 转换为 Document 对象
    • 3.2 对 Document 对象的解析
    • 3.3 标签解析
    1. 注册 BeanDefinition

1. 简介

  1. `IOC`容器的初始化过程分为三步骤:`Resource` 定位、`BeanDefinition` 的载入和解析,`BeanDefinition` 注册。

spring-201805281001
1、Resource 定位。我们一般用外部资源(如xml文件)来描述 Bean对象,所以在初始化IOC 容器的第一步就是需要定位这个外部资源。

  1. **2BeanDefinition 的载入和解析**。装载就是 `BeanDefinition` 的载入。`BeanDefinitionReader` 读取、解析 `Resource` 资源,也就是将用户定义的 `Bean` 表示成`IOC` 容器的内部数据结构:`BeanDefinition`。在`IOC` 容器内部维护着一个 `BeanDefinition Map` 的数据结构,在配置文件中每一个`<bean>`都对应着一个`BeanDefinition`对象。
  2. **3BeanDefinition 注册**。向`IOC`容器注册在第二步解析好的 `BeanDefinition`,这个过程是通过 `BeanDefinitionRegistery` 接口来实现的。在`IOC`容器内部其实是将第二个过程解析得到的`BeanDefinition`注入到一个 `HashMap`容器中,`IOC` 容器就是通过这个`HashMap`来维护这些 `BeanDefinition`的。在这里需要注意的一点是这个过程并没有完成依赖注入,依赖注入是发生在应用第一次调用 `getBean()` 向容器索要 `Bean`时。当然我们可以通过设置预处理,即对某个`Bean`设置 `lazyinit` 属性,那么这个 `Bean` 的依赖注入就会在容器初始化的时候完成。
  3. ClassPathResource resource = new ClassPathResource("bean.xml");
  4. DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
  5. XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
  6. reader.loadBeanDefinitions(resource);
  7. 1`ClassPathResource resource = new ClassPathResource("bean.xml");` 根据 `Xml` 配置文件创建 `Resource` 资源对象。`ClassPathResource` `Resource` 接口的子类,`bean.xml` 文件中的内容是我们定义的`Bean`信息。
  8. 2`DefaultListableBeanFactory factory = new DefaultListableBeanFactory();`创建一个 `BeanFactory``DefaultListableBeanFactory` `BeanFactory` 的一个子类,`BeanFactory` 作为一个接口,其实它本身是不具有独立使用的功能的,而`DefaultListableBeanFactory` 则是真正可以独立使用的`IOC` 容器,它是整个 `Spring IOC` 的始祖,。
  9. 3`XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);`:创建 `XmlBeanDefinitionReader`读取器,用于载入`BeanDefinition`
  10. 4`reader.loadBeanDefinitions(resource);`:开启`Bean` 的载入和注册进程,完成后的 `Bean` 放置在 `IOC` 容器中。

2. Resource 定位

  1. `Spring` 为了解决资源定位的问题,提供了两个接口:`Resource``ResourceLoader`,其中 `Resource` 接口是`Spring`统一资源的抽象接口,`ResourceLoader` 则是 `Spring` 资源加载的统一抽象。
  2. `Resource` 资源的定位需要`Resource` `ResourceLoader` 两个接口互相配合,在上面那段代码中 `new ClassPathResource("bean.xml")` 为我们定义了资源,那么 `ResourceLoader` 则是在什么时候初始化的呢?看 `XmlBeanDefinitionReader` 构造方法:
  3. public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
  4. super(registry);
  5. }
  6. 直接调用其父类 `AbstractBeanDefinitionReader`构造方法
  7. protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
  8. Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
  9. this.registry = registry;
  10. // Determine ResourceLoader to use.
  11. if (this.registry instanceof ResourceLoader) {
  12. this.resourceLoader = (ResourceLoader) this.registry;
  13. }
  14. else {
  15. this.resourceLoader = new PathMatchingResourcePatternResolver();
  16. }
  17. // Inherit Environment if possible
  18. if (this.registry instanceof EnvironmentCapable) {
  19. this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
  20. }
  21. else {
  22. this.environment = new StandardEnvironment();
  23. }
  24. }
  25. 核心在于 `resourceLoader` 字段,如果设置了 `ResourceLoader` 则用设置的,否则使用 `PathMatchingResourcePatternResolver`,该类是一个集大成者的 `ResourceLoader`

3. BeanDefinition 的载入和解析

  1. `reader.loadBeanDefinitions(resource);` 开启 `BeanDefinition`的解析过程。如下:
  2. public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
  3. return loadBeanDefinitions(new EncodedResource(resource));
  4. }
  5. 在这个方法会将资源 `resource` 包装成一个 `EncodedResource` 实例对象,然后调用 `loadBeanDefinitions()` 方法,而将 `Resource` 封装成 `EncodedResource` 主要是为了对 `Resource` 进行编码,保证内容读取的正确性。
  6. public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
  7. String filename = encodedResource.getResource().getFilename();
  8. if (StringUtils.endsWithIgnoreCase(filename, ".xml")) {
  9. // 如果是以 xml 结尾,则以标准的xml解析方式去加载 resource 资源
  10. return this.standardXmlBeanDefinitionReader.loadBeanDefinitions(encodedResource);
  11. } else {
  12. if (this.logger.isTraceEnabled()) {
  13. this.logger.trace("Loading Groovy bean definitions from " + encodedResource);
  14. }
  15. Closure<Object> beans = new Closure<Object>(this) {
  16. public Object call(Object... args) {
  17. GroovyBeanDefinitionReader.this.invokeBeanDefiningClosure((Closure)args[0]);
  18. return null;
  19. }
  20. };
  21. Binding binding = new Binding() {
  22. public void setVariable(String name, Object value) {
  23. if (GroovyBeanDefinitionReader.this.currentBeanDefinition != null) {
  24. GroovyBeanDefinitionReader.this.applyPropertyToBeanDefinition(name, value);
  25. } else {
  26. super.setVariable(name, value);
  27. }
  28. }
  29. };
  30. binding.setVariable("beans", beans);
  31. int countBefore = this.getRegistry().getBeanDefinitionCount();
  32. try {
  33. GroovyShell shell = new GroovyShell(this.getBeanClassLoader(), binding);
  34. shell.evaluate(encodedResource.getReader(), "beans");
  35. } catch (Throwable var7) {
  36. throw new BeanDefinitionParsingException(new Problem("Error evaluating Groovy script: " + var7.getMessage(), new Location(encodedResource.getResource()), (ParseState)null, var7));
  37. }
  38. int count = this.getRegistry().getBeanDefinitionCount() - countBefore;
  39. if (this.logger.isDebugEnabled()) {
  40. this.logger.debug("Loaded " + count + " bean definitions from " + encodedResource);
  41. }
  42. return count;
  43. }
  44. }
  45. public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
  46. Assert.notNull(encodedResource, "EncodedResource must not be null");
  47. if (logger.isTraceEnabled()) {
  48. logger.trace("Loading XML bean definitions from " + encodedResource);
  49. }
  50. // 获取正在被加载的 resources 资源
  51. Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
  52. if (currentResources == null) {
  53. currentResources = new HashSet<>(4);
  54. this.resourcesCurrentlyBeingLoaded.set(currentResources);
  55. }
  56. // 如果当前加载的 resource 资源之前没有被加载过,则进行保存
  57. if (!currentResources.add(encodedResource)) {
  58. throw new BeanDefinitionStoreException(
  59. "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
  60. }
  61. try {
  62. // 将资源文件转为 InputStream 的 IO 流
  63. InputStream inputStream = encodedResource.getResource().getInputStream();
  64. try {
  65. // 从 InputStream 中得到 XML 的解析源
  66. InputSource inputSource = new InputSource(inputStream);
  67. if (encodedResource.getEncoding() != null) {
  68. inputSource.setEncoding(encodedResource.getEncoding());
  69. }
  70. // 具体的读取过程
  71. return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
  72. }
  73. finally {
  74. inputStream.close();
  75. }
  76. }
  77. catch (IOException ex) {
  78. throw new BeanDefinitionStoreException(
  79. "IOException parsing XML document from " + encodedResource.getResource(), ex);
  80. }
  81. finally {
  82. // 加载完成后,将其删除
  83. currentResources.remove(encodedResource);
  84. if (currentResources.isEmpty()) {
  85. this.resourcesCurrentlyBeingLoaded.remove();
  86. }
  87. }
  88. }
  89. `encodedResource` 源中获取 `xml` 的解析源,调用 `doLoadBeanDefinitions()` 执行具体的解析过程。
  90. protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
  91. throws BeanDefinitionStoreException {
  92. try {
  93. Document doc = doLoadDocument(inputSource, resource);
  94. int count = registerBeanDefinitions(doc, resource);
  95. if (logger.isDebugEnabled()) {
  96. logger.debug("Loaded " + count + " bean definitions from " + resource);
  97. }
  98. return count;
  99. }
  100. // 省略很多catch代码
  101. }
  102. 在该方法中主要做两件事:

​ 1、根据 xml 解析源封装成相应的 Document 对象

​ 2、调用 registerBeanDefinitions() 开启 BeanDefinition 的解析注册过程。

3.1 转换为 Document 对象

  1. 调用 `doLoadDocument()` 会将 `Bean` 的定义资源转换为 `Document` 对象。
  2. protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
  3. return this.documentLoader.loadDocument(
  4. inputSource, getEntityResolver(), this.errorHandler,
  5. getValidationModeForResource(resource), isNamespaceAware());
  6. }
  7. `loadDocument()` 方法接受五个参数:
  • inputSource:加载 DocumentResource
  • entityResolver:解析文件的解析器
  • errorHandler:处理加载 Document 对象的过程的错误
  • validationMode:验证模式
  • namespaceAware:命名空间支持。如果要提供对 XML 名称空间的支持,则为true

    loadDocument() 在类 DefaultDocumentLoader 中提供了实现,如下:

    public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,

    1. ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
    2. // 创建文件解析工厂
    3. DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
    4. if (logger.isDebugEnabled()) {
    5. logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
    6. }
    7. // 创建文档解析器
    8. DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
    9. // 解析 Spring 的 Bean 定义资源
    10. return builder.parse(inputSource);

    }

    到这里,就已经将定义的 Bean 资源文件,载入并转换为 Document 对象了,那么下一步就是如何将其解析为 Spring IOC 管理的 Bean 对象并将其注册到容器中。这个过程有方法 registerBeanDefinitions() 实现。如下:

    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {

    1. // 创建 BeanDefinitionDocumentReader 来对 xml 格式的BeanDefinition 解析
    2. BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    3. // 获得容器中注册的Bean数量
    4. int countBefore = getRegistry().getBeanDefinitionCount();
    5. // 解析过程入口,这里使用了委派模式,BeanDefinitionDocumentReader只是个接口,
    6. // 具体的解析实现过程有实现类DefaultBeanDefinitionDocumentReader完成
    7. documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    8. return getRegistry().getBeanDefinitionCount() - countBefore;

    }

    首先创建BeanDefinition 的解析器 BeanDefinitionDocumentReader,然后调用 documentReader.registerBeanDefinitions() 开启解析过程,这里使用的是委派模式,具体的实现由子类 DefaultBeanDefinitionDocumentReader 完成。

    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {

    1. // 获得XML描述符
    2. this.readerContext = readerContext;
    3. logger.debug("Loading bean definitions");
    4. // 获得Document的根元素
    5. Element root = doc.getDocumentElement();
    6. // 解析根元素
    7. doRegisterBeanDefinitions(root);

    }

3.2 对 Document 对象的解析

  1. `Document` 对象中获取根元素 `root`,然后调用 `doRegisterBeanDefinitions()` 开启真正的解析过程。
  2. protected void doRegisterBeanDefinitions(Element root) {
  3. BeanDefinitionParserDelegate parent = this.delegate;
  4. this.delegate = createDelegate(getReaderContext(), root, parent);
  5. // 省略部分代码
  6. preProcessXml(root);
  7. parseBeanDefinitions(root, this.delegate);
  8. postProcessXml(root);
  9. this.delegate = parent;
  10. }
  11. `preProcessXml()``postProcessXml()` 为前置、后置增强处理,目前 `Spring`中都是空实现, `parseBeanDefinitions()` 是对根元素 root 的解析注册过程。
  12. protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
  13. // Bean定义的Document对象使用了Spring默认的XML命名空间
  14. if (delegate.isDefaultNamespace(root)) {
  15. // 获取Bean定义的Document对象根元素的所有子节点
  16. NodeList nl = root.getChildNodes();
  17. for (int i = 0; i < nl.getLength(); i++) {
  18. Node node = nl.item(i);
  19. // 获得Document节点是XML元素节点
  20. if (node instanceof Element) {
  21. Element ele = (Element) node;
  22. // Bean定义的Document的元素节点使用的是Spring默认的XML命名空间
  23. if (delegate.isDefaultNamespace(ele)) {
  24. // 使用Spring的Bean规则解析元素节点(默认解析规则)
  25. parseDefaultElement(ele, delegate);
  26. }
  27. else {
  28. // 没有使用Spring默认的XML命名空间,则使用用户自定义的解析规则解析元素节点
  29. delegate.parseCustomElement(ele);
  30. }
  31. }
  32. }
  33. }
  34. else {
  35. // Document 的根节点没有使用Spring默认的命名空间,则使用用户自定义的解析规则解析
  36. delegate.parseCustomElement(root);
  37. }
  38. }
  39. 迭代 `root` 元素的所有子节点,对其进行判断,若节点为默认命名空间,则调用 `parseDefaultElement()` 开启默认标签的解析注册过程,否则调用 `parseCustomElement()` 开启自定义标签的解析注册过程。

3.3 标签解析

  1. 若定义的元素节点使用的是`Spring` 默认命名空间,则调用 `parseDefaultElement()` 进行默认标签解析,如下:
  2. private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
  3. // 如果元素节点是<Import>导入元素,进行导入解析
  4. if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
  5. importBeanDefinitionResource(ele);
  6. }
  7. // 如果元素节点是<Alias>别名元素,进行别名解析
  8. else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
  9. processAliasRegistration(ele);
  10. }
  11. // 如果元素节点<Bean>元素,则进行Bean解析注册
  12. else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
  13. processBeanDefinition(ele, delegate);
  14. }
  15. // // 如果元素节点<Beans>元素,则进行Beans解析
  16. else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
  17. // recurse
  18. doRegisterBeanDefinitions(ele);
  19. }
  20. }
  21. 对于自定义标签则由 `parseCustomElement()` 负责解析。
  22. public BeanDefinition parseCustomElement(Element ele) {
  23. return parseCustomElement(ele, null);
  24. }
  25. public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
  26. String namespaceUri = getNamespaceURI(ele);
  27. if (namespaceUri == null) {
  28. return null;
  29. }
  30. NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
  31. if (handler == null) {
  32. error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
  33. return null;
  34. }
  35. return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
  36. }
  37. 获取节点的 `namespaceUri`,然后根据该 `namespaceuri`获取相对应的 `Handler`,调用 `Handler` `parse()` 方法即完成自定义标签的解析和注入。

4. 注册 BeanDefinition

  1. 经过上面的解析,则将 `Document` 对象里面的`Bean`标签解析成了一个个的 `BeanDefinition`,下一步则是将这些`BeanDefinition` 注册到`IOC` 容器中。动作的触发是在解析 `Bean` 标签完成后,如下:
  2. protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
  3. BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
  4. if (bdHolder != null) {
  5. bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
  6. try {
  7. // Register the final decorated instance.
  8. BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
  9. }
  10. catch (BeanDefinitionStoreException ex) {
  11. getReaderContext().error("Failed to register bean definition with name '" +
  12. bdHolder.getBeanName() + "'", ele, ex);
  13. }
  14. // Send registration event.
  15. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
  16. }
  17. }
  18. 调用 `BeanDefinitionReaderUtils.registerBeanDefinition()` 注册,其实这里面也是调用 `BeanDefinitionRegistry` `registerBeanDefinition()`来注册 BeanDefinition ,不过最终的实现是在`DefaultListableBeanFactory` 中实现,如下:
  19. @Override
  20. public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
  21. throws BeanDefinitionStoreException {
  22. // 省略一堆校验
  23. BeanDefinition oldBeanDefinition;
  24. oldBeanDefinition = this.beanDefinitionMap.get(beanName);
  25. // 省略一堆 if
  26. this.beanDefinitionMap.put(beanName, beanDefinition);
  27. }
  28. else {
  29. if (hasBeanCreationStarted()) {
  30. // Cannot modify startup-time collection elements anymore (for stable iteration)
  31. synchronized (this.beanDefinitionMap) {
  32. this.beanDefinitionMap.put(beanName, beanDefinition);
  33. List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
  34. updatedDefinitions.addAll(this.beanDefinitionNames);
  35. updatedDefinitions.add(beanName);
  36. this.beanDefinitionNames = updatedDefinitions;
  37. if (this.manualSingletonNames.contains(beanName)) {
  38. Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
  39. updatedSingletons.remove(beanName);
  40. this.manualSingletonNames = updatedSingletons;
  41. }
  42. }
  43. }
  44. else {
  45. // Still in startup registration phase
  46. this.beanDefinitionMap.put(beanName, beanDefinition);
  47. this.beanDefinitionNames.add(beanName);
  48. this.manualSingletonNames.remove(beanName);
  49. }
  50. this.frozenBeanDefinitionNames = null;
  51. }
  52. if (oldBeanDefinition != null || containsSingleton(beanName)) {
  53. resetBeanDefinition(beanName);
  54. }
  55. }
  56. private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
  57. 这段代码最核心的部分是这句 `this.beanDefinitionMap.put(beanName, beanDefinition)` ,所以注册过程也不是那么的高大上,就是利用一个 ConcurrentHashMap的集合对象来存放,key beanNamevalue BeanDefinition
  58. 至此,整个`IOC` 的初始化过程就已经完成了,从 `Bean` 资源的定位,转换为 `Document` 对象,接着对其进行解析,最后注册到`IOC`容器中,都已经完美地完成了。现在`IOC` 容器中已经建立了整个 `Bean` 的配置信息,这些 `Bean`可以被检索、使用、维护,他们是控制反转的基础,是后面注入 `Bean`的依赖。

在这里插入图片描述

发表评论

表情:
评论列表 (有 0 条评论,566人围观)

还没有评论,来说两句吧...

相关阅读