Spring技术内幕 :IoC容器的实现(三)—— IoC容器的初始化过程(一)

xiaoxiao2021-02-28  99

  在IoC容器实现系列的上一篇中,我们简单了解了IoC容器的两大系列:BeanFactory和ApplicationContext系列。了解了它们的设计思想与应用场景。在本篇博文中,我们将继续探索IoC容器的初始化过程。

  在上一篇中,我们知道IoC的初始化过程是由refresh()方法启动的,启动过程包括BeanDefinition的Resource定位、载入和注册三个过程。Spring将三个过程分开,使用不同的模块来完成,使得用户可以更加灵活的对三个过程进行剪裁或扩展,定义更加符合自己需求的IoC初始化过程。

Resource定位过程,指的是BeanDefinition的资源定位,定位过程类似于容器寻找数据的过程。由ResourceLoader统一的Resource接口来完成。BeanDefinition的载入,将用户定义好的Bean表示成IoC容器的内部数据结构(BeanDefinition)。通过BeanDefinition,使得IoC容器可以方便的对POJO对象进行管理。BeanDefinition的注册,通过调用BeanDefinitionRegistry接口的实现来完成。其实就是将BeanDefinition注入到一个HashMap中,IoC容器就是通过HashMap来持有这些BeanDefinition数据。

注意:IoC容器的初始化过程,一般不包含Bean依赖注入的实现。Bean定义的载入和依赖注入是两个独立的过程。

1 BeanDefinition的Resource定位

  在定位BeanDefinition是,如果使用纯粹的IoC容器,例如DefaultListableBeanFactory,则需要为它配置特定的读取器才能完成读取BeanDefinition的功能,但是这些更底层的容器可以提高定制IoC的灵活性。

  在这里,我们以FileSystemXmlApplicationContext为例,通过它分析ApplicationContext的实现是如何实现Resource的定位过程。下图为ApplicationContext的继承体系。

  从源码实现角度,近距离关心以FileSystemXmlApplicationContext为核心的继承体系,如下图所示。

  从中可以看到FileSystemXmlApplicationContext通过继承AbstractApplicationContext具备了ResourceLoader读入以Resource定义的BeanDefinition的能力。下面具体看看FileSystemXmlApplicationContext是如何实现的。

package org.springframework.context.support; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; /* FileSystemXmlApplicationContext使用的IoC容器是DefaultListableBeanFactory */ public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext { public FileSystemXmlApplicationContext() { } public FileSystemXmlApplicationContext(ApplicationContext parent) { super(parent); } // 这个构造函数的configuration包含的是BeanDefinition所在的文件路径 public FileSystemXmlApplicationContext(String configLocation) throws BeansException { this(new String[]{configLocation}, true, (ApplicationContext)null); } // 这个构造函数允许configuration包含多个BeanDefinition的文件路径 public FileSystemXmlApplicationContext(String... configLocations) throws BeansException { this(configLocations, true, (ApplicationContext)null); } // 这个构造函数允许configuration包含多个BeanDefinition的文件路径的同时,还允许指定自己的双亲IoC容器 public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException { this(configLocations, true, parent); } public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException { this(configLocations, refresh, (ApplicationContext)null); } // 在对象初始化过程中,调用refresh函数载入BeanDefinition // 这个refresh启动了BeanDefinition的载入过程,将在下面进行详细分析 public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); this.setConfigLocations(configLocations); if (refresh) { this.refresh(); } } // 应用文件系统中的Resource实现,通过构造一个FileSystemResource来得到一个在文件系统中定位的BeanDefinition // getResourceByPath是在BeanDefinitionReader的loadBeanDefinition中被调用的。 // loadBeanDefinition采用了模板模式,具体的定位实现实际上是由各个子类来完成的。 protected Resource getResourceByPath(String path) { if (path != null && path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path); } }

  通过上述代码,我们可以看到以XML文件方式存在的BeanDefinition都能够得到有效的处理,并且在构造方法中通过refresh方法来启动IoC容器的初始化。

  注意:FileSystempplicationContextContext是一个支持XML定义的BeanDefinition的ApplicationContext,可以指定以文件的形式读入BeanDefinition。在测试环境和独立应用环境中,这个ApplicationContext都是十分有用的。

  对BeanDefinition资源定位的过程是由refresh方法来触发的,大致调用过程如下图所示。

  在读入BeanDefinition的过程中需要使用BeanDefinitionReader,而关于这个读入器的配置,可以到FileSystemXmlApplicationContext的父类AbstractRefreshableApplicationContext中看看他是如何实现的。如果是其他类型的ApplicationContext,则会生成其他种类的Resource。

  现在我们重点研究AbstractRefreshableApplicationContext的refreshBeanFactory方法的实现。它通过调用createBeanFactory创建一个IoC容器供ApplicationContext使用,同时它启动了loadBeanDefinitions来载入BeanDefinition。

protected final void refreshBeanFactory() throws BeansException { // 如果原来已经有BeanFactory,则销毁并关闭,保证每次refreshBeanFactory后产生的为新的BeanFactory if (this.hasBeanFactory()) { this.destroyBeans(); this.closeBeanFactory(); } // 这里创建并设置持有的DefaultListableBeanFactory的地方,同时并调用loadBeanDefinitions载入BeanDefinition信息 try { DefaultListableBeanFactory beanFactory = this.createBeanFactory();    // 创建IoC容器 beanFactory.setSerializationId(this.getId()); this.customizeBeanFactory(beanFactory); this.loadBeanDefinitions(beanFactory);                                // 启动对BeanDefinition的载入 Object var2 = this.beanFactoryMonitor; synchronized(this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException var5) { throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5); } } // 这里就是在上下文中创建DefaultListableBeanFactory的地方,getInternalParentBeanFactory()的 // 具体实现可以参见AbstractApplicationContext中的实现,会根据容器已有的双亲IoC容器来生成 // DefaultListableBeanFactory的双亲IoC容器 protected DefaultListableBeanFactory createBeanFactory() { return new DefaultListableBeanFactory(this.getInternalParentBeanFactory()); } // 这里的载入Bean定义的有很多种方式载入,所以为抽象方法,交给具体容器完成相应的功能,委托给子类完成。 protected abstract void loadBeanDefinitions(DefaultListableBeanFactory var1) throws BeansException, IOException;

  其中具体资源的载入在XmlBeanDefinitionReader读入BeanDefinition时完成,具体的loadBeanDefinitions可以在XmlBeanDefinitionRead的父类AbstractBeanDefinitionReader中看到,如下所示。

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException { // 此处的ResourceLoader,使用的是DefaultResourceLoader ResourceLoader resourceLoader = this.getResourceLoader(); if (resourceLoader == null) { throw new BeanDefinitionStoreException("Cannot import bean definitions from location [" + location + "]: no ResourceLoader available"); } else { // 用于记录载入Bean的个数 int loadCount; // 调用DefaultResourceLoader的getResource完成具体的Resource定位 if (!(resourceLoader instanceof ResourcePatternResolver)) { Resource resource = resourceLoader.getResource(location); loadCount = this.loadBeanDefinitions((Resource)resource); if (actualResources != null) { actualResources.add(resource); } if (this.logger.isDebugEnabled()) { this.logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]"); } return loadCount; } else { // 通过对Resource的路径进行解析,得到Resource集合,这些集合指向定义好的BeanDefinition信息,可以使多个文件 try { Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location); loadCount = this.loadBeanDefinitions(resources); if (actualResources != null) { Resource[] var6 = resources; int var7 = resources.length; for(int var8 = 0; var8 < var7; ++var8) { Resource resource = var6[var8]; actualResources.add(resource); } } if (this.logger.isDebugEnabled()) { this.logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]"); } return loadCount; } catch (IOException var10) { throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", var10); } } } } // 对于取得Resource的具体过程,可以参考DefaultResourceLoader是怎样完成的。 public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); Iterator var2 = this.protocolResolvers.iterator(); Resource resource; do { if (!var2.hasNext()) { // 对路径的处理 if (location.startsWith("/")) { return this.getResourceByPath(location); } // 这里处理带有classpath标识的Resource if (location.startsWith("classpath:")) { return new ClassPathResource(location.substring("classpath:".length()), this.getClassLoader()); } // 这里处理URL表示的Resource定位 try { URL url = new URL(location); return new UrlResource(url); } catch (MalformedURLException var5) { // 如果既不是classpath,也不是URL表示的Resource,也不是普通的path,就将任务交给getResourceByPath() // 该方法为protected方法,默认实现是得到一个ClassPathContextResource,这个方法通常会用子类来实现 return this.getResourceByPath(location); } } ProtocolResolver protocolResolver = (ProtocolResolver)var2.next(); resource = protocolResolver.resolve(location, this); } while(resource == null); return resource; }

  在BeanDefinition的定位基础上,通过上面代码中返回的Resource对象就可以来进行BeanDefinition的载入工作了。下面,我们将开始介绍BeanDefinition的载入和解析。

2 BeanDefinition的载入和解析

  在第一小节中完成了对BeanDefinition的定位后,就到了整个BeanDefinition信息的载入过程,即把定义的BeanDefinition转化为一个Spring内部表示的数据结构的过程。这些BeanDefinition数据在IoC中通过一个HashMap进行保持和维护。

  我们依然从DefaultListableBeanFactory的设计入手,探索IoC容器是如何完成对BeanDefinition的载入的。在前面我们知道由refresh函数启动了IoC容器的初始化,现在我们来简单介绍一下它的实现。

  该方法在AbstractApplicationContext中,详细地描述了整个ApplicationContext的初始化过程,如BeanFactory的更新,MessageSource和PostProcessor的注册等。这个过程为Bean的声明周期提供了条件,具体代码如下所示。

public void refresh() throws BeansException, IllegalStateException { Object var1 = this.startupShutdownMonitor; // 加锁,保证线程间一致性 synchronized(this.startupShutdownMonitor) { this.prepareRefresh(); // 启动子类中refreshBeanFactory()方法。 ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory(); // 准备应用于上下文的BeanFactory this.prepareBeanFactory(beanFactory); try { // 设置BeanFactory的后置处理 this.postProcessBeanFactory(beanFactory); // 调用BeanFactory的后置处理器,这些后置处理器是在Bean定义总向容器中注册的。 this.invokeBeanFactoryPostProcessors(beanFactory); // 注册Bean的后处理器,在Bean创建过程中调用。 this.registerBeanPostProcessors(beanFactory); // 初始化上下文消息源 this.initMessageSource(); // 初始化上下文的事件机制 this.initApplicationEventMulticaster(); // 初始化其他特殊Bean this.onRefresh(); // 检查监听Bean,并且将这些Bean向容器中注册 this.registerListeners(); // 实例化所有的(non-lazy-init)单件 this.finishBeanFactoryInitialization(beanFactory); // 发布容器事件,结束refresh过程 this.finishRefresh(); } catch (BeansException var9) { if (this.logger.isWarnEnabled()) { this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9); } // 为了防止Bean资源占用,在异常处理中销毁已经在前面生成的Bean单件,并且重置‘active’标志 this.destroyBeans(); this.cancelRefresh(var9); throw var9; } finally { this.resetCommonCaches(); } } }

  进入refreshBeanFactory方法后,创建新的BeanFactory。实例代码已经在上文中出现过,这里边不再赘述。在建立好IoC容器后,就开始了初始化过程,比如BeanDefinition的载入,具体的交互过程如下图所示。

  这里调用的loadBeanDefinitions实际上为一个抽象方法,交给各个具体的容器实现该方法。在XML方式载入过程中,这个方法中在AbstractXmlApplicationContext实现,在这个loadBeanDefinitions中,初始化了读取器XmlBeanDefinitionReader,然后把读取器在IoC容器设置好,最后启动读取器来完成BeanDefinition的载入。

public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext { // 此处省略了类中的其他构造方法,这里是实现loadBeanDefinitions的地方,即根据上一小节中refreshBeanFactory得到的 // BeanFactory将其中的BeanDefinitions信息载入IoC容器。 protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // 创建XmlBeanDefinitionReader,并设置到BeanFactory中。此处使用的BeanFactory也是DefaultListableBeanFactory XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); beanDefinitionReader.setEnvironment(this.getEnvironment()); // 这里设置XmlBeanDefinitionReader,为XmlBeanDefinitionReader配置ResourceLoader // 因为DefaultResourceLoader是父类,所以可以直接使用this beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // 启动Bean定义信息的载入过程 this.initBeanDefinitionReader(beanDefinitionReader); this.loadBeanDefinitions(beanDefinitionReader); } protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) { reader.setValidating(this.validating); } protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { // 首先以Resource的方式得到配置文件资源的位置信息,看是否存在 Resource[] configResources = this.getConfigResources(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } // 以String的形式获得配置文件的位置 String[] configLocations = this.getConfigLocations(); if (configLocations != null) { reader.loadBeanDefinitions(configLocations); } } protected Resource[] getConfigResources() { return null; } }

  在这里我们仅仅使用XmlBeanDefinitionReader作为示例说明,因为Spring可以对应不同形式的BeanDefinition。如果使用了其他形式的BeanDefinition,则需要使用其他种类的BeanDefinitionReader完成数据载入工作。下面我们就来看看具体载入BeanDefinition的过程。因为在AbstractBeanDefinitionReader中loadBeanDefinitions方法为抽象方法,所有应该到具体的实现读取器中方法查看相应代码,代码清单如下所示。

// 这是调用的入口方法 public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return this.loadBeanDefinitions(new EncodedResource(resource)); } // 以XML形式载入BeanDefinition public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (this.logger.isInfoEnabled()) { this.logger.info("Loading XML bean definitions from " + encodedResource.getResource()); } Set<EncodedResource> currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { // 这里小编不是特别明白为什么HashSet的初始大小为4?求解 currentResources = new HashSet(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } // 如果将当前encodedResource加入到HashSet失败,则抛出异常。 if (!((Set)currentResources).add(encodedResource)) { throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } else { int var5; // 这里得到XML文件,并得到IO的InputSource,准备进行读取。 try { InputStream inputStream = encodedResource.getResource().getInputStream(); try { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } // 具体调用doLoadBeanDefinitions完成相应的BeanDefinition的载入 var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); } } catch (IOException var15) { throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var15); } finally { ((Set)currentResources).remove(encodedResource); if (((Set)currentResources).isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } return var5; } } public int loadBeanDefinitions(InputSource inputSource) throws BeanDefinitionStoreException { return this.loadBeanDefinitions(inputSource, "resource loaded through SAX InputSource"); } public int loadBeanDefinitions(InputSource inputSource, String resourceDescription) throws BeanDefinitionStoreException { return this.doLoadBeanDefinitions(inputSource, new DescriptiveResource(resourceDescription)); } // 具体的读取过程,从特定的XML文件中实际载入BeanDefinition的地方 protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { // 调用doLoadDocument方法得到XML文件的Doucment对象 Document doc = this.doLoadDocument(inputSource, resource); // 启动对BeanDefinition的详细解析。 return this.registerBeanDefinitions(doc, resource); } catch (BeanDefinitionStoreException var4) { throw var4; } catch (SAXParseException var5) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + var5.getLineNumber() + " in XML document from " + resource + " is invalid", var5); } catch (SAXException var6) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", var6); } catch (ParserConfigurationException var7) { throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, var7); } catch (IOException var8) { throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, var8); } catch (Throwable var9) { throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, var9); } } // 得到XML文件的Document对象,解析过程有documentLoader完成。 // documentLoader是DefaultDocumentLoader在定义documentLoader的地方创建 protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, this.getValidationModeForResource(resource), this.isNamespaceAware()); }

  在调用了registerBeanDefinitions()方法后,开始对BeanDefinition进行详细解析,而且此方法对载入的Bean还做了数量统计。

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { // 得到BeanDefinitionDocumentReader对XML的BeanDefinition进行解析 BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader(); int countBefore = this.getRegistry().getBeanDefinitionCount(); // 具体的解析过程在registerBeanDefinitions中完成 documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource)); // 返回载入Bean的个数 return this.getRegistry().getBeanDefinitionCount() - countBefore; }

  本篇博文就先到这里吧,虽然还没有写完,但本篇博文实在有点长了,所以在下一篇博文中我们将继续分析IoC初始化过程,总结也就在下一篇写啦。

转载请注明原文地址: https://www.6miu.com/read-2631074.html

最新回复(0)