在 BeanFactory 里只对 IOC 容器的基本行为作了定义,根本不关心你的 bean 是如何定义怎样加载的。 正如我们只关心工厂里得到什么的产品对象,至于工厂是怎么生产这些对象的,这个基本的接口不关心。
要知道工厂是如何产生对象的,我们需要看具体的 IOC 容器实现,Spring 提供了许多 IOC 容器的 实现。比如 XmlBeanFactory,ClasspathXmlApplicationContext 等。其中 XmlBeanFactory 就是针对最 基本的 IOC 容器的实现,这个 IOC 容器可以读取 XML 文件定义的 BeanDefinition(XML 文件中对 bean 的描述),如果说 XmlBeanFactory 是容器中的低配屌丝,ApplicationContext 应该算容器中的高帅富
/** * Create a new FileSystemXmlApplicationContext, loading the definitions * from the given XML files and automatically refreshing the context. * @param configLocations array of file paths * @throws BeansException if context creation failed */ publicFileSystemXmlApplicationContext(String... configLocations)throws BeansException { this(configLocations, true, null); }
AbstractApplicationContext构造方法中调用PathMatchingResourcePatternResolver的构造方法创建 Spring 资源加载器:
1 2 3 4 5
publicPathMatchingResourcePatternResolver(ResourceLoader resourceLoader){ Assert.notNull(resourceLoader, "ResourceLoader must not be null"); //设置 Spring 的资源加载器 this.resourceLoader = resourceLoader; }
//解析 Bean 定义资源文件的路径,处理多个资源文件字符串数组 publicvoidsetConfigLocations(String[] locations){ if (locations != null) { Assert.noNullElements(locations, "Config locations must not be null"); this.configLocations = new String[locations.length]; for (int i = 0; i < locations.length; i++) { // resolvePath 将字符串解析为路径的方法 this.configLocations[i] = resolvePath(locations[i]).trim(); } } else { this.configLocations = null; } }
通过这两个方法的源码我们可以看出,我们既可以使用一个字符串来配置多个 Spring Bean 定义资源 文件,也可以使用字符串数组,即下面两种方式都是可以的: 1.ClasspathResource res = new ClasspathResource(“a.xml,b.xml,……”); 多个资源文件路径之间可以是用” ,; /t/n”等分隔。 2.ClasspathResource res = new ClasspathResource(newString[]{“a.xml”,”b.xml”,……}); Spring IOC 容器在初始化时将配置的 Bean 定义资源文件定位为 Spring 封装的 Resource。 3.AbstractApplicationContext 的 refresh 函数载入 Bean 定义过程: Spring IOC 容器对 Bean 定义资源的载入是从 refresh()函数开始的,refresh()是一个模板方法, refresh()方法的作用是:在创建 IOC 容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭, 以保证在 refresh 之后使用的是新建立起来的 IOC 容器。refresh 的作用类似于对 IOC 容器的重启,在 新建立好的容器中对容器进行初始化,对 Bean 定义资源进行载入
protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)throws ParserConfigurationException { //创建文档解析工厂 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(namespaceAware); //设置解析 XML 的校验 if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) { factory.setValidating(true); if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) { factory.setNamespaceAware(true); try { factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE); } catch (IllegalArgumentException ex) { ParserConfigurationException pcex = new ParserConfigurationException( "Unable to validate using XSD: Your JAXP provider [" + factory + "] does not support XML Schema. Are you running on Java 1.with Apache Crimson? " + "Upgrade to Apache Xerces (or Java 1.5) for full XSD support."); pcex.initCause(ex); throw pcex; } } } return factory; }
该解析过程调用 JavaEE 标准的 JAXP 标准进行处理。 至此 Spring IOC 容器根据定位的 Bean 定义资源文件,将其加载读入并转换成为 Document 对象过程完成。 接下来我们要继续分析 Spring IOC 容器将载入的 Bean 定义资源文件转换为 Document 对象之后,是如 何将其解析为 Spring IOC 管理的 Bean 对象并将其注册到容器中的。
XmlBeanDefinitionReader 解析载入的 Bean 定义资源文件
XmlBeanDefinitionReader类中的doLoadBeanDefinitions方法是从特定XML 文件中实际载入Bean 定 义资源的方法,该方法在载入 Bean 定义资源之后将其转换为 Document 对象,接下来调用 registerBeanDefinitions 启动 Spring IOC 容器对 Bean 定义的解析过程,registerBeanDefinitions 方法源码如下:
//解析<Import>导入元素,从给定的导入路径加载 Bean 定义资源到 Spring IoC 容器中 protectedvoidimportBeanDefinitionResource(Element ele){ //获取给定的导入元素的 location 属性 String location = ele.getAttribute(RESOURCE_ATTRIBUTE); //如果导入元素的 location 属性值为空,则没有导入任何资源,直接返回 if (!StringUtils.hasText(location)) { getReaderContext().error("Resource location must not be empty", ele); return; } //使用系统变量值解析 location 属性值 location = SystemPropertyUtils.resolvePlaceholders(location); Set<Resource> actualResources = new LinkedHashSet<Resource>(4); //标识给定的导入元素的 location 是否是绝对路径 boolean absoluteLocation = false; try { absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute(); } catch (URISyntaxException ex) { //给定的导入元素的 location 不是绝对路径 } //给定的导入元素的 location 是绝对路径 if (absoluteLocation) { try { //使用资源读入器加载给定路径的 Bean 定义资源 int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources); if (logger.isDebugEnabled()) { logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]"); } } catch (BeanDefinitionStoreException ex) { getReaderContext().error( "Failed to import bean definitions from URL location [" + location + "]", ele, ex); } } else { //给定的导入元素的 location 是相对路径 try { int importCount; //将给定导入元素的 location 封装为相对路径资源 Resource relativeResource = getReaderContext().getResource().createRelative(location); //封装的相对路径资源存在 if (relativeResource.exists()) { //使用资源读入器加载 Bean 定义资源 importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource); actualResources.add(relativeResource); } //封装的相对路径资源不存在 else { //获取 Spring IOC 容器资源读入器的基本路径 String baseLocation = getReaderContext().getResource().getURL().toString(); //根据 Spring IoC 容器资源读入器的基本路径加载给定导入路径的资源 importCount = getReaderContext().getReader().loadBeanDefinitions( StringUtils.applyRelativePath(baseLocation, location), actualResources); } if (logger.isDebugEnabled()) { logger.debug("Imported " + importCount + " bean definitions from relative location [" + location + "]"); } } catch (IOException ex) { getReaderContext().error("Failed to resolve current resource location", ele, ex); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]", ele, ex); } } Resource[] actResArray = actualResources.toArray(new Resource[actualResources.size()]); //在解析完<Import>元素之后,发送容器导入其他资源处理完成事件 getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele)); }
//解析<Alias>别名元素,为 Bean 向 Spring IoC 容器注册别名 protectedvoidprocessAliasRegistration(Element ele){ //获取<Alias>别名元素中 name 的属性值 String name = ele.getAttribute(NAME_ATTRIBUTE); //获取<Alias>别名元素中 alias 的属性值 String alias = ele.getAttribute(ALIAS_ATTRIBUTE); boolean valid = true; //<alias>别名元素的 name 属性值为空 if (!StringUtils.hasText(name)) { getReaderContext().error("Name must not be empty", ele); valid = false; } //<alias>别名元素的 alias 属性值为空 if (!StringUtils.hasText(alias)) { getReaderContext().error("Alias must not be empty", ele); valid = false; } if (valid) { try { //向容器的资源读入器注册别名 getReaderContext().getRegistry().registerAlias(name, alias); } catch (Exception ex) { getReaderContext().error("Failed to register alias '" + alias + "' for bean with name '" + name + "'", ele, ex); } //在解析完<Alias>元素之后,发送容器别名处理完成事件 getReaderContext().fireAliasRegistered(name, alias, extractSource(ele)); } }
//解析 Bean 定义资源 Document 对象的普通元素 protectedvoidprocessBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate){ // BeanDefinitionHolder 是对 BeanDefinition 的封装,即 Bean 定义的封装类 //对 Document 对象中<Bean>元素的解析由 BeanDefinitionParserDelegate 实现 BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { //向 Spring IoC 容器注册解析得到的 Bean 定义,这是 Bean 定义向 IOC 容器注册的入口 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } //在完成向 Spring IoC 容器注册解析得到的 Bean 定义之后,发送注册事件 getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
通过上述 Spring IOC 容器对载入的 Bean 定义 Document 解析可以看出,我们使用 Spring 时,在 Spring 配置文件中可以使用<Import>元素来导入 IOC 容器所需要的其他资源,Spring IoC 容器在解析时会首 先将指定导入的资源加载进容器中。使用<Ailas>别名时,Spring IOC 容器首先将别名元素所定义的别 名注册到容器中。
对于既不是<Import>元素,又不是<Alias>元素的元素,即 Spring 配置文件中普通的<Bean>元素的解析 由 BeanDefinitionParserDelegate 类的 parseBeanDefinitionElement 方法来实现。
//解析<list>集合子元素 public List parseListElement(Element collectionEle, BeanDefinition bd){ //获取<list>元素中的 value-type 属性,即获取集合元素的数据类型 String defaultElementType = collectionEle.getAttribute(VALUE_TYPE_ATTRIBUTE); //获取<list>集合元素中的所有子节点 NodeList nl = collectionEle.getChildNodes(); //Spring 中将 List 封装为 ManagedList ManagedList<Object> target = new ManagedList<Object>(nl.getLength()); target.setSource(extractSource(collectionEle)); //设置集合目标数据类型 target.setElementTypeName(defaultElementType); target.setMergeEnabled(parseMergeAttribute(collectionEle)); //具体的<list>元素解析 parseCollectionElements(nl, target, bd, defaultElementType); return target; } //具体解析<list>集合元素,<array>、<list>和<set>都使用该方法解析 protectedvoidparseCollectionElements( NodeList elementNodes, Collection<Object> target, BeanDefinition bd, String defaultElementType){ //遍历集合所有节点 for (int i = 0; i < elementNodes.getLength(); i++) { Node node = elementNodes.item(i); //节点不是 description 节点 if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT)) { //将解析的元素加入集合中,递归调用下一个子元素 target.add(parsePropertySubElement((Element) node, bd, defaultElementType)); } } }
经过对 Spring Bean 定义资源文件转换的 Document 对象中的元素层层解析,Spring IOC 现在已经将 XML 形式定义的 Bean 定义资源文件转换为 Spring IOC 所识别的数据结构——BeanDefinition,它是 Bean 定义资源文件中配置的 POJO 对象在 Spring IOC 容器中的映射,我们可以通过 AbstractBeanDefinition 为入口,看到了 IOC 容器进行索引、查询和操作。
通过 Spring IOC 容器对 Bean 定义资源的解析后,IOC 容器大致完成了管理 Bean 对象的准备工作,即 初始化过程,但是最为重要的依赖注入还没有发生,现在在 IOC 容器中 BeanDefinition 存储的只是一 些静态信息,接下来需要向容器注册 Bean 定义信息才能全部完成 IoC 容器的初始化过程
//存储注册信息的 BeanDefinition privatefinal Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(); //向 IoC 容器注册解析的 BeanDefiniton publicvoidregisterBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException { Assert.hasText(beanName, "Bean name must not be empty"); Assert.notNull(beanDefinition, "BeanDefinition must not be null"); //校验解析的 BeanDefiniton if (beanDefinition instanceof AbstractBeanDefinition) { try { ((AbstractBeanDefinition) beanDefinition).validate(); } catch (BeanDefinitionValidationException ex) { thrownew BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", ex); } } //注册的过程中需要线程同步,以保证数据的一致性 synchronized (this.beanDefinitionMap) { Object oldBeanDefinition = this.beanDefinitionMap.get(beanName); //检查是否有同名的 BeanDefinition 已经在 IOC 容器中注册,如果已经注册, //并且不允许覆盖已注册的 Bean,则抛出注册失败异常 if (oldBeanDefinition != null) { if (!this.allowBeanDefinitionOverriding) { thrownew BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName + "': There is already [" + oldBeanDefinition + "] bound."); } else { //如果允许覆盖,则同名的 Bean,后注册的覆盖先注册的 if (this.logger.isInfoEnabled()) { this.logger.info("Overriding bean definition for bean '" + beanName + "': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]"); } } } //IOC 容器中没有已经注册同名的 Bean,按正常注册流程注册 else { this.beanDefinitionNames.add(beanName); this.frozenBeanDefinitionNames = null; } this.beanDefinitionMap.put(beanName, beanDefinition); //重置所有已经注册过的 BeanDefinition 的缓存 resetBeanDefinition(beanName); } }
至此,Bean 定义资源文件中配置的 Bean 被解析过后,已经注册到 IOC 容器中,被容器管理起来,真正 完成了 IOC 容器初始化所做的全部工作。现在 IOC 容器中已经建立了整个 Bean 的配置信息,这些 BeanDefinition 信息已经可以使用,并且可以被检索,IOC 容器的作用就是对这些注册的 Bean 定义信 息进行处理和维护。这些的注册的 Bean 定义信息是 IoC 容器控制反转的基础,正是有了这些注册的数 据,容器才可以进行依赖注入。
总结
现在通过上面的代码,总结一下 IOC 容器初始化的基本步骤:
初始化的入口在容器实现中的 refresh()调用来完成
对 bean 定义载入 IOC 容器使用的方法是 loadBeanDefinition,
其中的大致过程如下: 通过 ResourceLoader 来完成资源文件位置的定位,DefaultResourceLoader 是默 认的实现,同时上下文本身就给出了 ResourceLoader 的实现,可以从类路径,文件系统,URL 等方式来 定为资源位置。如果是 XmlBeanFactory 作为 IOC 容器,那么需要为它指定 bean 定义的资源,也就是说 bean 定义文件时通过抽象成 Resource 来被 IOC 容器处理的,容器通过 BeanDefinitionReader 来完成 定义信息的解析和 Bean 信息的注册,往往使用的是 XmlBeanDefinitionReader 来解析 bean 的 xml 定义 文件-实际的处理过程是委托给 BeanDefinitionParserDelegate来完成的,从而得到bean的定义信息, 这些信息在 Spring 中使用 BeanDefinition 对象来表示-这个名字可以让我们想到 loadBeanDefinition,RegisterBeanDefinition 这些相关方法-他们都是为处理BeanDefinitin 服务的, 容器解析得到 BeanDefinitionIoC 以后,需要把它在 IOC 容器中注册,这由 IOC 实 现 BeanDefinitionRegistry 接口来实现。注册过程就是在 IOC 容器内部维护的一个 HashMap 来保存得 到的 BeanDefinition 的过程。这个 HashMap 是 IOC 容器持有 bean 信息的场所,以后对 bean 的操作都 是围绕这个 HashMap 来实现的.
然后我们就可以通过 BeanFactory 和 ApplicationContext 来享受到 SpringIOC 的服务了,在使用 IOC 容器的时候,我们注意到除了少量粘合代码,绝大多数以正确 IOC 风格编写的应用程序代码完全不用关 心如何到达工厂,因为容器将把这些对象与容器管理的其他对象钩在一起。基本的策略是把工厂放到已 知的地方,最好是放在对预期使用的上下文有意义的地方,以及代码将实际需要访问工厂的地方。Spring 本身提供了对声明式载入 web 应用程序用法的应用程序上下文,并将其存储在 ServletContext 中的框架 实现。
在使用 SpringIOC 容器的时候我们还需要区别两个概念
BeanFactory 和 FactoryBean
BeanFactory 功能性工厂,专门用来生产bean的,如果是生产车的就叫做CarFactory,是 IOC 容器的编程抽象,比如ApplicationContext,XmlBeanFactory 等,这些都是 IOC 容器的具体表现,需要使用什么样的容器由客户决定,但 Spring 为我们提供了丰富的选择。
FactoryBean 是由spring工厂生产出来的bean, 只是一个可以在 IOC 而容器中被管理的一个bean,是对各种处理过程和资源使用的抽象,FactoryBean 在需要时产生另一个对象,而不返回FactoryBean 本身,我们可以把它看成是一个抽象工厂,对它的调用返回的是工厂生产的产品。所有的FactoryBean 都实现特殊的 org.springframework.beans.factory.FactoryBean 接口,当使用容器中FactoryBean 的时候,该容器不会返回 FactoryBean 本身,而是返回其生成的对象。Spring 包括了大部分的通用资源和服务访问抽象的 FactoryBean 的实现,其中包括:对 JNDI 查询的处理,对代理对象的处理,对事务性代理的处理,对 RMI 代理的处理等,这些我们都可以看成是具体的工厂,看成是 Spring 为我们建立好的工厂。也就是说 Spring 通过使用抽象工厂模式为我们准备了一系列工厂来生产一些特定的对象,免除我们手工重复的工作,我们要使用时只需要在 IOC 容器里配置好就能很方便的使用了