Spring容器的基本实现 核心类介绍
在了解核心类前,需要对benas的工程结构有初步的认识
DefaultListableBeanFactory XmlBeanFactory 继承自DefaultListableBeanFactory ,而DefaultListableBeanFactory 是整个bean加载的核心 部分,是Spring 注册及加载bean 的默认实现 。
XmlBeanFactory 对DefaultListableBeanFactory 类进行了扩展,主要用于从XML 文档中读取BeanDefinition ,对于注册及获取bean 都是使用从父类DefaultListableBeanFactory 继承的方法去实现,而唯独与父类不同的个性化实现就是增加了XmlBeanDefinitionReader 类型的reader 属性。在XmlBeanFactory 中主要使用reader 属性对资源文件进行提取和注册。
XmlBeanDefinitionReader XML 配置文件的读取是Spring 中的重要功能,因为Spring 的大部分功能都是以配置 作为切入点的。
一些相关类的功能如下:
ResourceLoader :定义资源加载器,根据给定的资源文件地址返回对应的Resource
BeanDefinitionReader :主要定义资源文件读取并转换为BeanDefinition 的各个功能
EnvironmentCapable :定义获取Environment 方法
DocumentLoader :定义从资源文件加载到转换 为Document 的功能
AbstractBeanDefinitionReader :对EnvironmentCapable 、BeanDefinitionReader 类定义的功能进行实现
BeanDefinitionDocumentReader :定义读取Document 并注册BeanDefinition 功能
BeanDefinitionParserDelegate :定义解析Element 的各种方法
整个XML配置文件读取 的大致流程如下:
通过继承 自AbstractBeanDefinitionReader 中的方法,来使用ResourceLoader 将资源文件路径转换 为对应的Resource文件。
通过DocumentLoader 对Resource 文件进行转换 ,将Resource 文件转换为Document 文件。
通过实现接口BeanDefinitionDocumentReader 的DefaultBeanDefinitionDocumentReader 类对Document 进行解析,并使用BeanDefinitionParserDelegate 对Element 进行解析。
容器的基础XmlBeanFactory
接下来分析以下代码的执行过程
1 BeanFactory bf = new XmlBeanFactory (new ClassPathResource ("beanFactoryTest.xml" ));
配置文件封装 Spring 配置文件的读取是通过ClassPathResource 进行封装的。在Java 中,将不同来源的资源抽象成URL ,通过注册不同的handler 来处理不同来源的资源的读取逻辑。Spring 对其内部使用到的资源实现了自己的抽象结构:Resource 接口封装底层资源。以下分析使用Resource 实例作为参数的构造方法。
XmlBeanFactory.java
1 2 3 public XmlBeanFactory (Resource resource) throws BeansException { this (resource, null ); }
1 2 3 4 5 public XmlBeanFactory (Resource resource, BeanFactory parentBeanFactory) throws BeansException { super (parentBeanFactory); this .reader.loadBeanDefinitions(resource); }
在资源加载前调用父类构造函数初始化super(parentBeanFactory)
AbstractAutowireCapableBeanFactory.java
1 2 3 4 5 6 public AbstractAutowireCapableBeanFactory () { super (); ignoreDependencyInterface(BeanNameAware.class); ignoreDependencyInterface(BeanFactoryAware.class); ignoreDependencyInterface(BeanClassLoaderAware.class); }
ignoreDependencyInterface 方法的作用是忽略 给定接口的自动装配 功能。
加载Bean 在XmlBeanFactory 构造函数中调用了XmlBeanDefinitionReader 类型的reader 属性提供的方法this.reader.loadBeanDefinitions(resource)
,此为整个资源加载的切入点。
XmlBeanDefinitionReader.java
1 2 3 4 @Override public int loadBeanDefinitions (Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource (resource)); }
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 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); } Set<EncodedResource> currentResources = this .resourcesCurrentlyBeingLoaded.get(); if (currentResources == null ) { currentResources = new HashSet <>(4 ); this .resourcesCurrentlyBeingLoaded.set(currentResources); } if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException ( "Detected cyclic loading of " + encodedResource + " - check your import definitions!" ); } try { InputStream inputStream = encodedResource.getResource().getInputStream(); try { InputSource inputSource = new InputSource (inputStream); if (encodedResource.getEncoding() != null ) { inputSource.setEncoding(encodedResource.getEncoding()); } return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { 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(); } } }
封装资源文件。当进入XmlBeanDefinitionReader 后首先对参数Resource 使用EncodedResource 类进行封装。
获取输入流。从Resource 中获取对应的InputStream 并构造InputSource 。
通过构造的InputSource 实例和Resource 实例继续调用函数doLoadBeanDefinitions 。
XmlBeanDefinitionReader.java
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 protected int doLoadBeanDefinitions (InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc = doLoadDocument(inputSource, resource); return registerBeanDefinitions(doc, resource); } 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); } }
获取对XML 文件的验证模式 。
加载XML 文件,并得到对应的Document 。
根据返回的Document注册Bean 信息。
获取XML的验证模式 DTD与XSD区别 DTD(Document Type Definition)即文档类型定义,是一种XML约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分。DTD是一种保证XML文档格式正确的有效方法,可以通过比较XML文档和DTD文件来看文档是否符合规范,元素和标签使用是否正确。
一个DTD文档包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体或符号规则。
要使用DTD验证模式的时候需要在XML文件的头部声明,以下是使用DTD声明方式的代码:
1 2 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0//EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.dtd">
XML Schema语言就是XSD(XML Schemas Definition)。XML Schema描述了XML文档的结构。可以用一个指定的XML Schema来验证某个XML文档,以检查该XML文档是否符合其要求。文档设计者可以通过XML Schema指定一个XML文档所允许的结构和内容,并可据此检查一个XML文档是否是有效的。XML Schema本身是一个XML文档,它符合XML语法结构。可以用通用的XML解析器解析它。
在使用XML Schema文档对XML实例文档进行检验,除了要声明名称空间外(xmlns= http://www.springframework.org/schema/beans),还必须指定该名称空间所对应的XML Schema文档的存储位置。通过schemaLocation属性来指定名称空间所对应的XML Schema文档的存储位置,它包含两个部分,一部分是名称空间的URI,另一部分就是该名称空间所标识的XML Schema文件位置或URL地址(xsi:schemaLocation=”http://www.springframework.org/schema/beans http://www . springframework.org/schema/beans/spring-beans.xsd)。
验证模式的读取 Spring 通过getValidationModeForResource 方法来获取对应资源的验证模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 protected int getValidationModeForResource (Resource resource) { int validationModeToUse = getValidationMode(); if (validationModeToUse != VALIDATION_AUTO) { return validationModeToUse; } int detectedMode = detectValidationMode(resource); if (detectedMode != VALIDATION_AUTO) { return detectedMode; } return VALIDATION_XSD; }
获取Document 经过了验证模式准备的步骤就可以进行Document 加载了,同样XmlBeanFactoryReader 类对于文档读取没有亲力亲为,而是委托给了DocumentLoader 去执行,这里的DocumentLoader 是个接口,而真正调用的是DefaultDocumentLoader ,解析代码如下:
DefaultDocumentLoader.java
1 2 3 4 5 6 7 8 9 10 11 @Override public Document loadDocument (InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); if (logger.isDebugEnabled()) { logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]" ); } DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); return builder.parse(inputSource); }
EntityResolver用法 通过上述代码可以发现,entityResolver 作为参数传入,该参数通过getEntityResolver 方法返回而来
1 2 3 4 5 6 7 8 9 10 11 12 13 protected EntityResolver getEntityResolver () { if (this .entityResolver == null ) { ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader != null ) { this .entityResolver = new ResourceEntityResolver (resourceLoader); } else { this .entityResolver = new DelegatingEntityResolver (getBeanClassLoader()); } } return this .entityResolver; }
对于解析一个XML ,SAX 首先读取该XML 文档上的声明 ,根据声明去寻找相应的DTD 定义,以便对文档进行一个验证。默认的寻找规则即通过网络 来下载相应的DTD 规则,有时候网络中断,这里就会报错。
EntityResolver 的作用就是项目本身就可以提供一个如何寻找DTD 声明的方法,即由程序来实现寻找DTD声明 的过程,比如将DTD 文件放到项目某处,在实现时直接将此文档读取并返回给SAX 即可。这样就避免了通过网络来寻找相应的声明。
解析及注册BeanDefinitions 在doLoadBeanDefinitions 方法中,document 获取到以后,会和resource 一起被传入到registerBeanDefinitions 方法这进行提取及注册bean 。
1 2 Document doc = doLoadDocument(inputSource, resource);return registerBeanDefinitions(doc, resource);
XmlBeanDefinitionReader.java
1 2 3 4 5 6 7 8 9 10 public int registerBeanDefinitions (Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
进入registerBeanDefinitions 方法内部分析过程
1 2 3 4 5 6 7 @Override public void registerBeanDefinitions (Document doc, XmlReaderContext readerContext) { this .readerContext = readerContext; logger.debug("Loading bean definitions" ); Element root = doc.getDocumentElement(); doRegisterBeanDefinitions(root); }
doRegisterBeanDefinitions 方法是真正加载解析XML 文件的核心逻辑 部分
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 protected void doRegisterBeanDefinitions (Element root) { BeanDefinitionParserDelegate parent = this .delegate; this .delegate = createDelegate(getReaderContext(), root, parent); 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 ; } } } preProcessXml(root); parseBeanDefinitions(root, this .delegate); postProcessXml(root); this .delegate = parent; }
profile属性的使用 通过分析上述代码,首先对profile 进行处理
profile 的作用是可以同时在配置文件中部署多套配置 ,比如测试环境,开发环境,生产环境,这样可以很方便的进行切换 不同环境的配置。
解析并注册BeanDefinition
通过parseBeanDefinitions 方法进行bean 的注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 protected void parseBeanDefinitions (Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { 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; if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
在Spring 的XML 配置中对于Bean 的声明有两种,一种是默认 的,使用parseDefaultElement 方法解析,另一种是自定义 声明的,需要用户实现一些接口及配置后,使用parseCustomElement 方法去注册解析bean 。