1、基本用法
1public class MySpringBean { 2 private String beanName = "beanName"; 3 4 public String getBeanName() { 5 return beanName; 6 } 7 8 public void setBeanName(String beanName) { 9 this.beanName = beanName; 10 } 11} 12
1<beans xmlns="http://www.springframework.org/schema/beans" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns:context="http://www.springframework.org/schema/context" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans 5 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> 6 7 <bean id="myBeanName" class="com.reray.spring.study.MySpringBean"/> 8 9</beans> 10
1@Test 2public void test() { 3 XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("application.xml")); 4 MySpringBean beanName = (MySpringBean) xmlBeanFactory.getBean("myBeanName"); 5 System.out.println(beanName.getBeanName()); 6} 7
2、核心类介绍
2.1 DefaultListableBeanFactory
XmlBeanFactory 继承自 DefaultListableBeanFactory,DefaultListableBeanFactory 是整个 bean 加载的核心部分,是 Spring 注册和加载 bean 的默认实现。XmlBeanFactory 和 DefaultListableBeanFactory 不同地方在于 XmlBeanFactory 使用了自定义的 xml 读取器 XmlBeanDefinitionReader 个性化的 BeanDefinition 读取。DefaultListableBeanFactory 的结构如下
XmlBeanFactory 对 DefaultListableBeanFactory 进行拓展,主要用于从 xml 文档中读取 BeanDefinition,注册和获取 bean 都是使用的父类的 DefaultListableBeanFactory 方法实现,增加了 XmlBeanDefinitionReader 对资源文件进行读取和注册
2.2 XmlBeanDefinitionReader
XmlBeanDefinitionReader 对资源文件进行读取、解析和注册
- ResourceLoader
- BeanDefinitionReader
- EnvironmentCapable
- DocumentLoader
- AbstractBeanDefinitionReader
- BeanDefinitionDocumentReader
- BeanDefinitionParserDelegate
(1)通过 AbstractBeanDefinitionReader 的 ResourceLoader 将资源文件路径转化为 Resource 文件
(2)通过 DocumentLoader 对 Resource 转化为 Document 文件
(3)通过 BeanDefinitionDocumentReader 的实现类 DefaultBeanDefinitionDocumentReader 对 Document 进行解析,并通过 BeanDefinitionParserDelegate 对 Element 进行解析
2.3 容器基础 XmlBeanFactory
1XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("application.xml")); 2
上面代码执行逻辑
- 调用 ClassPathResource 构造函数创建 Resource 资源文件实例
- Resource 传入 XmlBeanFactory 后调用 loadBeanDefinitions
2.3.1 配置文件封装
在 Java 中将不同来源的资源对象抽象为 URL,通过注册不同的 handler(URLStreamHandler)来处理不同来源的资源读取逻辑。Spring 对其内部使用到的资源实现了自己的抽象结构:Resource 接口来封装底层资源。
1public interface InputStreamSource { 2 InputStream getInputStream() throws IOException; 3} 4
1public interface Resource extends InputStreamSource { 2 boolean exists(); 3 boolean isReadable(); 4 boolean isOpen(); 5 URL getURL() throws IOException; 6 URI getURI() throws IOException; 7 File getFile() throws IOException; 8 long contentLength() throws IOException; 9 long lastModified() throws IOException; 10 Resource createRelative(String var1) throws IOException; 11 String getFilename(); 12 String getDescription(); 13} 14
InputStreamSource 只有 getInputStream() 返回 InputStream,可以返回任何 InputStream 类。Resource 接口抽象了所有 Spring 内部使用到的底层资源,例如 File、URL、ClassPath 等,不同来源的资源文件都有其 Resource 实现:文件(FileSystemResource)、Classpath 资源(ClassPathResource)、URL 资源(UrlResource)、InputStream 资源(InputStreamResource)、Byte 数组(ByteArrayResource)等。
- exists:存在性
- isReadable:可读性
- isOpen:是否处于打开状态
- getURL:获取 URL
- getURI:获取 URI
- getFile:获取 File 类型
- lastModified:获取资源最后一次被修改的时间戳
- getFilename:获取文件名
- createRelative:基于当前资源创建一个相对资源
- getDescription:在错误处理中打印信息
通过 Resource 相关类对配置文件进行封装后配置文件的读取工作交给 XmlBeanDefinitionReader 处理。
1public XmlBeanFactory(Resource resource) throws BeansException { 2 this(resource, null); 3} 4 5public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { 6 super(parentBeanFactory); 7 this.reader.loadBeanDefinitions(resource); 8} 9
this.reader.loadBeanDefinitions(resource) 是资源加载的真正实现,在此之前调用父类的构造函数初始化过程 super(parentBeanFactory),为父类 AbstractAutowireCapableBeanFactory 的构造函数。
1public AbstractAutowireCapableBeanFactory() { 2 super(); 3 ignoreDependencyInterface(BeanNameAware.class); 4 ignoreDependencyInterface(BeanFactoryAware.class); 5 ignoreDependencyInterface(BeanClassLoaderAware.class); 6} 7
ignoreDependencyInterface 的主要功能是忽略给定接口的自动装配功能,自动装配功能比如 A 中有属性 B,A 在初始化的过程中会默认初始化 B,也是 Spring 的一个重要特性。在某些情况下 B 不会被初始化,例如 B 实现了 BeanNameAware 接口,典型应用是通过其他方式解析 Application 上下文注册依赖,类似 BeanFactory 通过 BeanFactoryAware 注入或者 ApplicationContext 通过 ApplicationContextAware 注入。
2.3.2 加载 Bean
XmlBeanDefinitionReader 的 loadBeanDefinitions 方法是整个资源加载的切入点
- new EncodedResource(resource)
- loadBeanDefinitions(new EncodedResource(resource))
- encodedResource.getResource().getInputStream()
- new InputSource(inputStream)
- doLoadBeanDefinitions(inputSource, encodedResource.getResource())
调用逻辑如下:
- 对 Resource 进行 EncodedResource 类封装,目的是考虑 Resource 可能存在的编码要求情况
- 获取 Resource 对应的 InputStream 并构造 InputSource,后续通过 SAX 读取 xml 文件
- 根据 InputSource 和 Resource 调用核心方法 doLoadBeanDefinitions
1public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { 2 return loadBeanDefinitions(new EncodedResource(resource)); 3} 4
EncodedResource 的作用是对资源文件的编码进行处理,主要逻辑在 getReader 方法中
1public Reader getReader() throws IOException { 2 if (this.charset != null) { 3 return new InputStreamReader(this.resource.getInputStream(), this.charset); 4 } else { 5 return this.encoding != null ? new InputStreamReader(this.resource.getInputStream(), this.encoding) : new InputStreamReader(this.resource.getInputStream()); 6 } 7} 8
回到 loadBeanDefinitions(new EncodedResource(resource)) 方法
1public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { 2 Assert.notNull(encodedResource, "EncodedResource must not be null"); 3 if (logger.isInfoEnabled()) { 4 logger.info("Loading XML bean definitions from " + encodedResource.getResource()); 5 } 6 7 // 记录已加载的资源 8 Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); 9 if (currentResources == null) { 10 currentResources = new HashSet<EncodedResource>(4); 11 this.resourcesCurrentlyBeingLoaded.set(currentResources); 12 } 13 if (!currentResources.add(encodedResource)) { 14 throw new BeanDefinitionStoreException( 15 "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); 16 } 17 try { 18 // 获取 InputStream 19 InputStream inputStream = encodedResource.getResource().getInputStream(); 20 try { 21 // 封装为 org.xml.sax.InputSource 便于后面处理 xml 文件 22 InputSource inputSource = new InputSource(inputStream); 23 if (encodedResource.getEncoding() != null) { 24 inputSource.setEncoding(encodedResource.getEncoding()); 25 } 26 // 核心处理部分 27 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); 28 } 29 finally { 30 inputStream.close(); 31 } 32 } 33 catch (IOException ex) { 34 throw new BeanDefinitionStoreException( 35 "IOException parsing XML document from " + encodedResource.getResource(), ex); 36 } 37 finally { 38 currentResources.remove(encodedResource); 39 if (currentResources.isEmpty()) { 40 this.resourcesCurrentlyBeingLoaded.remove(); 41 } 42 } 43} 44
接下来进入核心处理方法 doLoadBeanDefinitions
1protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) 2 throws BeanDefinitionStoreException { 3 try { 4 int validationMode = getValidationModeForResource(resource); 5 Document doc = this.documentLoader.loadDocument( 6 inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware()); 7 return registerBeanDefinitions(doc, resource); 8 } 9 catch (BeanDefinitionStoreException ex) { 10 throw ex; 11 } 12 catch (SAXParseException ex) { 13 throw new XmlBeanDefinitionStoreException(resource.getDescription(), 14 "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); 15 } 16 catch (SAXException ex) { 17 throw new XmlBeanDefinitionStoreException(resource.getDescription(), 18 "XML document from " + resource + " is invalid", ex); 19 } 20 catch (ParserConfigurationException ex) { 21 throw new BeanDefinitionStoreException(resource.getDescription(), 22 "Parser configuration exception parsing XML from " + resource, ex); 23 } 24 catch (IOException ex) { 25 throw new BeanDefinitionStoreException(resource.getDescription(), 26 "IOException parsing XML document from " + resource, ex); 27 } 28 catch (Throwable ex) { 29 throw new BeanDefinitionStoreException(resource.getDescription(), 30 "Unexpected exception parsing XML document from " + resource, ex); 31 } 32} 33
不考虑异常处理,上面代码逻辑如下:
- 获取 xml 文件的验证模式
getValidationModeForResource - 加载 xml 文件,获取对应的 Document
loadDocument - 根据 Document 注册 Bean 信息
registerBeanDefinitions
接下来对这三个步骤进行分析
2.4 获取 xml 的验证模式
xml 文件的验证模式保证 xml 文件的正确性,常见的验证模式为 DTD 和 XSD
2.4.1 DTD 和 XSD 区别
DTD(Document Type Definition)即文档类型定义,是一种 xml 约束模式语言,是 xml 文件的验证机制,属于 xml 文件组成的一部分。DTD 是一种保证 xml 文档格式正确的有效方法,可以通过比较 xml 文档和 DTD 文件来看 xml 是否符合规范,元素和标签是否正确。DTD 文档包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体或符号规则。
Spring 中使用 DTD 文件的声明方式如下
1<?xml version="1.0" encoding="UTF-8"?> 2<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" 3 "http://www.springframework.org/dtd/spring-beans-2.0.dtd"> 4<beans> 5... 6</beans> 7
Spring 的 spring-beans-2.0.dtd 如下
1<!ELEMENT beans ( 2 description?, 3 (import | alias | bean)* 4)> 5 6<!ATTLIST beans default-lazy-init (true | false) "false"> 7<!ATTLIST beans default-merge (true | false) "false"> 8<!ATTLIST beans default-autowire (no | byName | byType | constructor | autodetect) "no"> 9<!ATTLIST beans default-dependency-check (none | objects | simple | all) "none"> 10<!ATTLIST beans default-init-method CDATA #IMPLIED> 11<!ATTLIST beans default-destroy-method CDATA #IMPLIED> 12 13<!ELEMENT description (#PCDATA)> 14... 15
XML Schemas 是 XSD(XML Schemas Definition),描述了 xml 文档的结构,可以来指定 xml 文档所允许的结构,并由此来校验 xml 文档的结构是否是有效的。XML Schemas 本身就是一个 xml 文档,符合 xml 的语法结构,可以用通用的 xml 解析器解析。
XML Schemas 需要声明的部分如下:
- 名称空间
xmlns="http://www.springframework.org/schema/beans - XML Schemas 的存储位置 schemaLocation
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd- 名称空间 URI
- 该名称空间所标识的 XML Schemas 文件位置或 URL 地址
1<?xml version="1.0" encoding="UTF-8"?> 2<beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans 5 http://www.springframework.org/schema/beans/spring-beans.xsd"> 6... 7</beans> 8
Spring 的 spring-beans-3.0.xsd 如下
1<?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 3<xsd:schema xmlns="http://www.springframework.org/schema/beans" 4 xmlns:xsd="http://www.w3.org/2001/XMLSchema" 5 targetNamespace="http://www.springframework.org/schema/beans"> 6 7 <xsd:import namespace="http://www.w3.org/XML/1998/namespace"/> 8 9 <xsd:annotation> 10.... 11
2.4.2 验证模式的读取
1protected int getValidationModeForResource(Resource resource) { 2 // VALIDATION_AUTO 3 int validationModeToUse = getValidationMode(); 4 // 如果手动指定了验证模式则使用指定验证模式 5 if (validationModeToUse != VALIDATION_AUTO) { 6 return validationModeToUse; 7 } 8 // 自动检测 9 int detectedMode = detectValidationMode(resource); 10 if (detectedMode != VALIDATION_AUTO) { 11 return detectedMode; 12 } 13 return VALIDATION_XSD; 14} 15
上面代码如果指定了验证模式(可通过 XmlBeanDefinitionReader 的 public void setValidationMode(int validationMode) 指定)则用指定的验证模式,否则通过 detectValidationMode 自动检测,自动检测验证模式的有专门处理类 XmlValidationModeDetector 的 detectValidationMode(InputStream inputStream) 实现
1protected int detectValidationMode(Resource resource) { 2 if (resource.isOpen()) { 3 throw new BeanDefinitionStoreException( 4 "Passed-in Resource [" + resource + "] contains an open stream: " + 5 "cannot determine validation mode automatically. Either pass in a Resource " + 6 "that is able to create fresh streams, or explicitly specify the validationMode " + 7 "on your XmlBeanDefinitionReader instance."); 8 } 9 10 InputStream inputStream; 11 try { 12 inputStream = resource.getInputStream(); 13 } 14 catch (IOException ex) { 15 throw new BeanDefinitionStoreException( 16 "Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " + 17 "Did you attempt to load directly from a SAX InputSource without specifying the " + 18 "validationMode on your XmlBeanDefinitionReader instance?", ex); 19 } 20 21 try { 22 // 自动检测验证模式 23 return this.validationModeDetector.detectValidationMode(inputStream); 24 } 25 catch (IOException ex) { 26 throw new BeanDefinitionStoreException("Unable to determine validation mode for [" + 27 resource + "]: an error occurred whilst reading from the InputStream.", ex); 28 } 29} 30
XmlValidationModeDetector 的 detectValidationMode(InputStream inputStream) 方法如下
1public int detectValidationMode(InputStream inputStream) throws IOException { 2 BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); 3 4 byte var4; 5 try { 6 // 是否为 DTD 验证模式 7 boolean isDtdValidated = false; 8 9 while(true) { 10 String content; 11 if ((content = reader.readLine()) != null) { 12 content = this.consumeCommentTokens(content); 13 if (this.inComment || !StringUtils.hasText(content)) { 14 continue; 15 } 16 17 if (this.hasDoctype(content)) { 18 isDtdValidated = true; 19 } else if (!this.hasOpeningTag(content)) { 20 continue; 21 } 22 } 23 24 int var5 = isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD; 25 return var5; 26 } 27 } catch (CharConversionException var9) { 28 var4 = VALIDATION_AUTO; 29 } finally { 30 reader.close(); 31 } 32 33 return var4; 34} 35
1private boolean hasDoctype(String content) { 2 return content.indexOf("DOCTYPE") > -1; 3} 4
Spring 自动检测验证模式的方法是判断是否包含 DOCTYPE,若包含则为 DTD,否则为 XSD
2.5 获取 Document
获取到 xml 验证模式后需要进行 Document 加载,使用的是 DocumentLoader 接口的 DefaultDocumentLoader 实现类 loadDocument 方法进行加载,代码如下
1private DocumentLoader documentLoader = new DefaultDocumentLoader(); 2 3Document doc = this.documentLoader.loadDocument( 4 inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware()); 5
1public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, 2 ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { 3 4 DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); 5 if (logger.isDebugEnabled()) { 6 logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]"); 7 } 8 DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); 9 return builder.parse(inputSource); 10} 11
通过 SAX 解析 xml 文档
- 创建 DocumentBuilderFactory
- 通过 DocumentBuilderFactory 创建 DocumentBuilder
- 使用 DocumentBuilder 进行解析
分析下 loadDocument 方法的 EntityResolver 参数,getEntityResolver 方法代码如下:
1protected EntityResolver getEntityResolver() { 2 if (this.entityResolver == null) { 3 ResourceLoader resourceLoader = getResourceLoader(); 4 if (resourceLoader != null) { 5 this.entityResolver = new ResourceEntityResolver(resourceLoader); 6 } 7 else { 8 this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader()); 9 } 10 } 11 return this.entityResolver; 12} 13
2.5.1 EntityResolver 用法
对于解析一个 xml,SAX 会首先读取 xml 文档上的声明,根据声明去寻找相应的 DTD 定义来对文档进行验证。默认的寻找规则通过网络(声明 DTD 的 URI 地址)进行下载 DTD 声明然后验证。但是下载遇到网络中断或者不可用就会报错。
EntityResolver 的作用是项目本身可以提供寻找 DTD 声明的方法,由程序来实现寻找 DTD 的过程。EntityResolver 接口代码如下:
1public interface EntityResolver { 2 public abstract InputSource resolveEntity (String publicId, 3 String systemId) 4 throws SAXException, IOException; 5} 6
接收两个参数 publicId 和 systemId,返回 InputSource
(1)如果是 XSD 验证模式的配置文件,代码如下:
1<?xml version="1.0" encoding="UTF-8"?> 2<beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans 5 http://www.springframework.org/schema/beans/spring-beans.xsd"> 6 7</beans> 8
其中关键部分是 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 两个参数为:
publicId: nullsystemId: http://www.springframework.org/schema/beans/spring-beans.xsd
(2)如果是 DTD 验证模式的配置文件,代码如下:
1<?xml version="1.0" encoding="UTF-8"?> 2<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" 3"http://www.springframework.org/dtd/spring-beans-2.0.dtd"> 4<beans> 5... 6</beans> 7
上面的两个参数为:
publicId: -//SPRING//DTD BEAN 2.0//ENsystemId: http://www.springframework.org/dtd/spring-beans-2.0.dtd
如果将验证文件通过网络下载用户体验会有延迟,一般做法是将验证文件放到自己的工程里。在 Spring 中使用的是 DelegatingEntityResolver 作为 EntityResolver 的实现类,resolveEntity 实现方法如下:
1public static final String DTD_SUFFIX = ".dtd"; 2public static final String XSD_SUFFIX = ".xsd"; 3 4public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { 5 if (systemId != null) { 6 if (systemId.endsWith(DTD_SUFFIX)) { 7 // DTD 文件从这里解析 8 return this.dtdResolver.resolveEntity(publicId, systemId); 9 } 10 else if (systemId.endsWith(XSD_SUFFIX)) { 11 // XSD 文件从这里解析 12 return this.schemaResolver.resolveEntity(publicId, systemId); 13 } 14 } 15 return null; 16} 17
从上面可以看到,DTD 和 XSD 文件使用不同的解析器进行解析
加载 DTD 类型的 BeansDtdResolver 截取 systemId 作为最后的 .dtd 然后从当前路径下寻找,代码如下:
1private static final String DTD_EXTENSION = ".dtd"; 2 3private static final String[] DTD_NAMES = {"spring-beans-2.0", "spring-beans"}; 4 5public InputSource resolveEntity(String publicId, String systemId) throws IOException { 6 if (logger.isTraceEnabled()) { 7 logger.trace("Trying to resolve XML entity with public ID [" + publicId + 8 "] and system ID [" + systemId + "]"); 9 } 10 if (systemId != null && systemId.endsWith(DTD_EXTENSION)) { 11 int lastPathSeparator = systemId.lastIndexOf("/"); 12 for (String DTD_NAME : DTD_NAMES) { 13 int dtdNameStart = systemId.indexOf(DTD_NAME); 14 if (dtdNameStart > lastPathSeparator) { 15 String dtdFile = systemId.substring(dtdNameStart); 16 if (logger.isTraceEnabled()) { 17 logger.trace("Trying to locate [" + dtdFile + "] in Spring jar"); 18 } 19 try { 20 Resource resource = new ClassPathResource(dtdFile, getClass()); 21 InputSource source = new InputSource(resource.getInputStream()); 22 source.setPublicId(publicId); 23 source.setSystemId(systemId); 24 if (logger.isDebugEnabled()) { 25 logger.debug("Found beans DTD [" + systemId + "] in classpath: " + dtdFile); 26 } 27 return source; 28 } 29 catch (IOException ex) { 30 if (logger.isDebugEnabled()) { 31 logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in class path", ex); 32 } 33 } 34 35 } 36 } 37 } 38 return null; 39} 40
加载 XSD 类型的 PluggableSchemaResolver 默认从 META-INF/spring.schemas 文件中找到 systemId 对应的 XSD 文件并加载,代码如下:
1public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas"; 2 3public InputSource resolveEntity(String publicId, String systemId) throws IOException { 4 if (logger.isTraceEnabled()) { 5 logger.trace("Trying to resolve XML entity with public id [" + publicId + 6 "] and system id [" + systemId + "]"); 7 } 8 9 if (systemId != null) { 10 String resourceLocation = getSchemaMappings().get(systemId); 11 if (resourceLocation != null) { 12 Resource resource = new ClassPathResource(resourceLocation, this.classLoader); 13 try { 14 InputSource source = new InputSource(resource.getInputStream()); 15 source.setPublicId(publicId); 16 source.setSystemId(systemId); 17 if (logger.isDebugEnabled()) { 18 logger.debug("Found XML schema [" + systemId + "] in classpath: " + resourceLocation); 19 } 20 return source; 21 } 22 catch (FileNotFoundException ex) { 23 if (logger.isDebugEnabled()) { 24 logger.debug("Couldn't find XML schema [" + systemId + "]: " + resource, ex); 25 } 26 } 27 } 28 } 29 return null; 30} 31 32/** 33 * Load the specified schema mappings lazily. 34 */ 35private Map<String, String> getSchemaMappings() { 36 if (this.schemaMappings == null) { 37 synchronized (this) { 38 if (this.schemaMappings == null) { 39 if (logger.isDebugEnabled()) { 40 logger.debug("Loading schema mappings from [" + this.schemaMappingsLocation + "]"); 41 } 42 try { 43 Properties mappings = 44 PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader); 45 if (logger.isDebugEnabled()) { 46 logger.debug("Loaded schema mappings: " + mappings); 47 } 48 Map<String, String> schemaMappings = new ConcurrentHashMap<String, String>(mappings.size()); 49 CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings); 50 this.schemaMappings = schemaMappings; 51 } 52 catch (IOException ex) { 53 throw new IllegalStateException( 54 "Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex); 55 } 56 } 57 } 58 } 59 return this.schemaMappings; 60} 61
2.6 解析及注册 BeanDefinitions
获取到 Document 后,接下来对 registerBeanDefinitions(doc, resource) 进行分析,代码如下:
1public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { 2 // 使用 DefaultBeanDefinitionDocumentReader 实例化 BeanDefinitionDocumentReader 3 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); 4 // 设置环境变量 5 documentReader.setEnvironment(this.getEnvironment()); 6 // 实例化 BeanDefinitionReader 时默认传入 BeanDefinitionRegistry,实现是 DefaultListableBeanFactory。记录统计前的 BeanDefinition 的加载个数 7 int countBefore = getRegistry().getBeanDefinitionCount(); 8 // 加载及注册 bean 9 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); 10 // 记录本次加载的 BeanDefinition 个数 11 return getRegistry().getBeanDefinitionCount() - countBefore; 12} 13
BeanDefinitionDocumentReader 是一个接口,实例化为 DefaultBeanDefinitionDocumentReader,进入后发现核心方法 registerBeanDefinitions 的重要目的之一是提取 root,以便再次使用 root 作为参数继续 BeanDefinition 的注册,代码如下:
1public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { 2 this.readerContext = readerContext; 3 logger.debug("Loading bean definitions"); 4 Element root = doc.getDocumentElement(); 5 doRegisterBeanDefinitions(root); 6} 7
可以看到核心逻辑的底部是 doRegisterBeanDefinitions 方法,代码如下:
1public static final String PROFILE_ATTRIBUTE = "profile"; 2 3protected void doRegisterBeanDefinitions(Element root) { 4 // 处理 profile 属性 5 String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); 6 if (StringUtils.hasText(profileSpec)) { 7 Assert.state(this.environment != null, "Environment must be set for evaluating profiles"); 8 String[] specifiedProfiles = StringUtils.tokenizeToStringArray( 9 profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); 10 if (!this.environment.acceptsProfiles(specifiedProfiles)) { 11 return; 12 } 13 } 14 15 BeanDefinitionParserDelegate parent = this.delegate; 16 this.delegate = createHelper(this.readerContext, root, parent); 17 18 // 解析前置处理,留给子类实现 19 preProcessXml(root); 20 // 解析并注册 21 parseBeanDefinitions(root, this.delegate); 22 // 解析后置处理,留给子类实现 23 postProcessXml(root); 24 25 this.delegate = parent; 26} 27
可以看到 preProcessXml 和 postProcessXml 代码为空,这是面向对象设计方法中,一个类要么是面向继承设计,要么是 final 修饰,而 DefaultBeanDefinitionDocumentReader 没有被修饰为 final,这也是模板方法模式,子类可以继承 DefaultBeanDefinitionDocumentReader 重写这两个方法在 Bean 解析前后做出一些处理。
2.6.1 profile 属性的使用
profile 属性可以根据不同的环境(例如开发、测试、生产)来激活不同的配置,在上面的代码方法中,Spring 会先获取 beans 是否定义了 profile 属性,如果定义了需要从环境变量中获取,因此断言 environment 不为空,因为 profile 可能为多个,因此拆分解析验证每个 profile 是否符合环境变量中的定义。例子如下:
1<?xml version="1.0" encoding="UTF-8"?> 2<beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans 5 http://www.springframework.org/schema/beans/spring-beans.xsd"> 6 7 <beans profile="dev"> 8 <bean id="myBeanName" class="com.reray.spring.study.MySpringBeanDev"/> 9 </beans> 10 11 <beans profile="prod"> 12 <bean id="myBeanName" class="com.reray.spring.study.MySpringBeanProd"/> 13 </beans> 14 15</beans> 16
2.6.2 解析注册 BeanDefinition
处理完 profile 后对 xml 读取,进入 parseBeanDefinitions(root, this.delegate) 代码如下:
1protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { 2 if (delegate.isDefaultNamespace(root)) { 3 NodeList nl = root.getChildNodes(); 4 for (int i = 0; i < nl.getLength(); i++) { 5 Node node = nl.item(i); 6 if (node instanceof Element) { 7 Element ele = (Element) node; 8 if (delegate.isDefaultNamespace(ele)) { 9 // 对 Bean 处理 10 parseDefaultElement(ele, delegate); 11 } 12 else { 13 // 对 Bean 处理 14 delegate.parseCustomElement(ele); 15 } 16 } 17 } 18 } 19 else { 20 delegate.parseCustomElement(root); 21 } 22} 23
Spring 中 xml 配置有两种 Bean 声明方式,默认的和自定义的。如果节点使用默认命名空间则采用 parseDefaultElement 进行解析,否则使用 parseCustomElement 解析。判断是默认的还是自定义的则使用 delegate.isDefaultNamespace 进行判断,使用 node.getNamespaceURI() 获取命名空间后和 http://www.springframework.org/schema/beans 进行对比
1public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans"; 2 3public boolean isDefaultNamespace(String namespaceUri) { 4 return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri)); 5} 6 7public boolean isDefaultNamespace(Node node) { 8 return isDefaultNamespace(getNamespaceURI(node)); 9} 10
1public String getNamespaceURI(Node node) { 2 return node.getNamespaceURI(); 3} 4
《Spring 容器的基本实现》 是转载文章,点击查看原文。

