BeanDefinition是Spring中很重要的一个部分,通过加载bean的配置的,把bean的配置信息转换为BeanDefinition对象,这个对象中就是一个对bean的描述相关信息。beanFactory就可以按照beanDefinition的描述对bean进行创建,beanDefinition 存在于beanFactory工厂中。

XmlBeanFactory是读取Spring配置文件的入口类,我们通过这个类作为入口,一层一层的扒开如何加载bean的配置信息

  1. public class XmlBeanFactory extends DefaultListableBeanFactory {
  2. // XmlBeanDefinitionReaer 作为bean的
  3. private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
  4. public XmlBeanFactory(Resource resource) throws BeansException {
  5. // 调用内部的构造方法
  6. this(resource, null);
  7. }
  8. public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
  9. // 忽略给定接口的自动装配
  10. super(parentBeanFactory);
  11. // 加载配置核心代码
  12. // 传入xml配置文件路径
  13. this.reader.loadBeanDefinitions(resource);
  14. }
  15. }

1. 加载Xml文件

加载指定的xml文件,通过构造函数指定配置文件

  1. XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("beans.xml"));

1.1. 配置文件封装

Spring 的配置文件读取是通过ClassPathResource 进行封装的,

2. 资源加载

资源文件的加载是通过XmlBeanDefinitionReader类来实现的,在这个类中有一个loadBeanDefinition()方法。
首先对资源文件使用EncodeResource进行封装

  1. public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
  2. // 读取配置文件
  3. // 在读取配置文件前,将配置文件对象封装成EncodedResource对象,目的是当设置了编码时,Spring会使用相应的编码作为配置文件输入流的编码
  4. return loadBeanDefinitions(new EncodedResource(resource));
  5. }

使用EncodeResource封装资源文件的目的是对资源文件进行编码处理,当设置了编码属性的时候Spring会使用相应的编码作为输入流的编码。当获取输入流时,会根据编码返回输入流。

  1. public Reader getReader() throws IOException {
  2. if (this.charset != null) {
  3. return new InputStreamReader(this.resource.getInputStream(), this.charset);
  4. }
  5. else if (this.encoding != null) {
  6. return new InputStreamReader(this.resource.getInputStream(), this.encoding);
  7. }
  8. else {
  9. return new InputStreamReader(this.resource.getInputStream());
  10. }
  11. }

对资源文件Resource封装后,进入文件加载的核心方法

  1. public 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);
  5. }
  6. // 记录已经加载过的资源文件(配置文件)
  7. Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
  8. if (currentResources == null) {
  9. currentResources = new HashSet<>(4);
  10. this.resourcesCurrentlyBeingLoaded.set(currentResources);
  11. }
  12. if (!currentResources.add(encodedResource)) {
  13. throw new BeanDefinitionStoreException(
  14. "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
  15. }
  16. try {
  17. // 从EncodedResource对象中获取已经封装的Resource对象并再次从Resource中获取InputStream资源文件输入流
  18. InputStream inputStream = encodedResource.getResource().getInputStream();
  19. try {
  20. // 将资源文件输入流封装到InputSource对象
  21. InputSource inputSource = new InputSource(inputStream);
  22. if (encodedResource.getEncoding() != null) {
  23. inputSource.setEncoding(encodedResource.getEncoding());
  24. }
  25. // 读取读取资源文件的核心方法
  26. return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
  27. }
  28. finally {
  29. // 关闭输入流
  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. }

加载xml文件

  1. protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
  2. throws BeanDefinitionStoreException {
  3. try {
  4. // 加载XML文件,得到对应的Document
  5. Document doc = doLoadDocument(inputSource, resource);
  6. // 根据Document注册Bean信息
  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. }

3. 获取XML的验证模式

在了解XML文件的验证模式之前,先介绍下什么是XML的验证模式。XML的验证模式保证了XML的正确性,比较常见的验证模式有两种:DTD和XSD

3.1. DTD验证模式

DTD(Document Type Definition)文档类型定义,是一种XML约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分。DTD是一种能保证XML文档格式正确的有效方法,可以对比XML文档和DTD文件来看文档是否规范,元素和标签使用是否正确。一个DTD文档包含:元素的定义规则,元素间的关系定义规则,元素可使用的属性,可使用的实体或符号规则。

3.2. XSD验证模式

XML Schema 语言就是XSD(XML Schemas Definition)。XSD 描述了XML文档的结构,可以用一个XSD来验证某个XML文档,以检查XML文档是否符合要求。文档设计者可以根据XSD来指定XML文档所允许的结构和内容,并可检查XML文档是否有效。XSD 本身就是XML文档,它符合XML语法结构,可以用通用的XML解析器来解析。

  1. protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
  2. return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
  3. getValidationModeForResource(resource), isNamespaceAware());
  4. }

3.3. 验证模式获取

Spring 在加载xml文档之前会先获取xml文档的验证模式,这个验证模式就是通过getValidationModeForResource()方法来获取对应资源的验证模式

  1. protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
  2. return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
  3. getValidationModeForResource(resource), isNamespaceAware());
  4. }

分析getValidationModeForResource()方法是如何来获取xml的验证模式的。

  1. protected int getValidationModeForResource(Resource resource) {
  2. int validationModeToUse = getValidationMode();
  3. // 手动指定验证模式
  4. if (validationModeToUse != VALIDATION_AUTO) {
  5. return validationModeToUse;
  6. }
  7. // 未手动指定则自动检测验证模式
  8. int detectedMode = detectValidationMode(resource);
  9. if (detectedMode != VALIDATION_AUTO) {
  10. return detectedMode;
  11. }
  12. // Hmm, we didn't get a clear indication... Let's assume XSD,
  13. // since apparently no DTD declaration has been found up until
  14. // detection stopped (before finding the document's root tag).
  15. return VALIDATION_XSD;
  16. }

getValidationModeForResource()方法很简单,里面的逻辑如果指定了验证模式,就使用指定的验证模式。如果没有指定验证模式,就自动是被验证模式。手动指定验证模式可以通过XmlBeanDefinitionReader类中的setValidationMode 来设置。

3.3.1. 手动设置验证模式

  1. // 伪代码
  2. XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader();
  3. reader.setValidationMode(XmlValidationModeDetector.VALIDATION_NONE);

3.3.2. 自动识别验证模式

自动识别验证模式的逻辑由detectValidationMode()方法完成,然后交由XmlValidationModeDetector类的detectValidationMode()方法识别验证模式

  1. protected 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. InputStream inputStream;
  10. try {
  11. inputStream = resource.getInputStream();
  12. }
  13. catch (IOException ex) {
  14. throw new BeanDefinitionStoreException(
  15. "Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
  16. "Did you attempt to load directly from a SAX InputSource without specifying the " +
  17. "validationMode on your XmlBeanDefinitionReader instance?", ex);
  18. }
  19. try {
  20. // 由XmlValidationModeDetector 识别验证模式
  21. return this.validationModeDetector.detectValidationMode(inputStream);
  22. }
  23. catch (IOException ex) {
  24. throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
  25. resource + "]: an error occurred whilst reading from the InputStream.", ex);
  26. }
  27. }

XmlValidationModeDetector类的detectValidationMode()方法

  1. public int detectValidationMode(InputStream inputStream) throws IOException {
  2. // Peek into the file to look for DOCTYPE.
  3. BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
  4. try {
  5. boolean isDtdValidated = false;
  6. String content;
  7. while ((content = reader.readLine()) != null) {
  8. // 读取行
  9. content = consumeCommentTokens(content);
  10. // 如果读取的是空行或者注释行略过
  11. if (this.inComment || !StringUtils.hasText(content)) {
  12. continue;
  13. }
  14. if (hasDoctype(content)) {
  15. isDtdValidated = true;
  16. break;
  17. }
  18. // 读取到<开始符号,验证模式一定会在开始符号之前
  19. if (hasOpeningTag(content)) {
  20. // End of meaningful data...
  21. break;
  22. }
  23. }
  24. // 返回验证模式
  25. return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
  26. }
  27. catch (CharConversionException ex) {
  28. // 由调用方决定用哪种验证模式
  29. // 如果获取验证模式异常
  30. return VALIDATION_AUTO;
  31. }
  32. finally {
  33. reader.close();
  34. }
  35. }

4. 获取Document

经过xml文档验证模式的获取工作后,接下来就是加载Document了。读取文档XmlBeanFactoryReader类会委托给DocumentLoader接口去执行,默认实现类是DefaultDocumentLoader

  1. public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
  2. ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
  3. // 创建DocumentBuilderFactory
  4. DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
  5. if (logger.isTraceEnabled()) {
  6. logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
  7. }
  8. // 创建DocumentBuilder
  9. DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
  10. // 获取Document
  11. return builder.parse(inputSource);
  12. }

4.1. 创建DocumentBuilderFactory

创建DocumentBuilderFactory 在Spring中没有什么特殊的地方,仅创建一个工厂对象

  1. protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
  2. throws ParserConfigurationException {
  3. // 实例化DocumentBulderFactory 对象
  4. DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
  5. factory.setNamespaceAware(namespaceAware);
  6. if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
  7. factory.setValidating(true);
  8. // xml文档校验类型是XSD
  9. if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
  10. factory.setNamespaceAware(true);
  11. try {
  12. factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
  13. }
  14. catch (IllegalArgumentException ex) {
  15. ParserConfigurationException pcex = new ParserConfigurationException(
  16. "Unable to validate using XSD: Your JAXP provider [" + factory +
  17. "] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
  18. "Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
  19. pcex.initCause(ex);
  20. throw pcex;
  21. }
  22. }
  23. }
  24. return factory;
  25. }

4.2. 创建DocumentBuilder

  1. protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory,
  2. @Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler)
  3. throws ParserConfigurationException {
  4. // 实例化DocumentBuilder 对象
  5. DocumentBuilder docBuilder = factory.newDocumentBuilder();
  6. // 设置EntityResolver
  7. if (entityResolver != null) {
  8. docBuilder.setEntityResolver(entityResolver);
  9. }
  10. // 异常处理Hander
  11. if (errorHandler != null) {
  12. docBuilder.setErrorHandler(errorHandler);
  13. }
  14. return docBuilder;
  15. }

4.3. 获取EntityResolver

在4.2中创建DocumentBuilder时,会给它赋值EntityResolver。它的获取方式很简单,是在XmlBeanDefinitionReader类中调用DefaultDocumentLoader类时传入的。
EntityResolver的作用是项目本身就可以提供一个如何寻找DTD声明的方法,即由程序来实现如何寻找DTD声明的过程,比如我们将DTD放在项目的某个位置,在实现时直接将此文档读取并返回给SAX即可,这样避免了通过网络来寻找相应的声明。

  1. // XmlBeanDefinitionReader类中获取EntityResolver的方法
  2. protected EntityResolver getEntityResolver() {
  3. if (this.entityResolver == null) {
  4. ResourceLoader resourceLoader = getResourceLoader();
  5. // 指定EntityResolver
  6. if (resourceLoader != null) {
  7. this.entityResolver = new ResourceEntityResolver(resourceLoader);
  8. }
  9. else {
  10. // 默认EntityResolver实现类
  11. this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
  12. }
  13. }
  14. return this.entityResolver;
  15. }

4.3.1. 默认实现DelegatingEntityResolver

DelegatingEntityResolver 作为EntityResolver接口的默认实现,在它的构造方法中创建了两个EntityResolver对象

  1. public DelegatingEntityResolver(@Nullable ClassLoader classLoader) {
  2. // DTD 解析对象
  3. this.dtdResolver = new BeansDtdResolver();
  4. // 从/META-INF/spring.schemas解析
  5. this.schemaResolver = new PluggableSchemaResolver(classLoader);
  6. }

DelegatingEntityResolver 类中解析方法,逻辑很简单就是决定采用哪中解析方式来解析

  1. public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId)
  2. throws SAXException, IOException {
  3. if (systemId != null) {
  4. // DTD解析
  5. if (systemId.endsWith(DTD_SUFFIX)) {
  6. return this.dtdResolver.resolveEntity(publicId, systemId);
  7. }
  8. // META-INF/spring.schemas解析
  9. else if (systemId.endsWith(XSD_SUFFIX)) {
  10. return this.schemaResolver.resolveEntity(publicId, systemId);
  11. }
  12. }
  13. // Fall back to the parser's default behavior.
  14. return null;
  15. }

5. 解析及注册BeanDefinition

在获取了Document后,接下来就是解析Document提取及注册bean。回到第2节中,在doLoadBeanDefinitions()方法中调用完获取Document方法后,仅接着就是调用registerBeanDefinitions()方法来注册bean

  1. public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
  2. // 实例化DefaultBeanDefinitionDocumentReader 对象
  3. // DefaultBeanDefinitionDocumentReader 是BeanDefinitionDocumentReader 接口的实现
  4. BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
  5. // 记录统计前的BeanDefinition 加载个数
  6. int countBefore = getRegistry().getBeanDefinitionCount();
  7. // 加载及注册bean
  8. documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
  9. // 本次加载的BeanDefinition 加载个数
  10. return getRegistry().getBeanDefinitionCount() - countBefore;
  11. }

registerBeanDefinitions()方法本身没有加载及注册bean方法中,只提取Document的root,具体的注册交给doRegisterBeanDefinitions()方法

  1. public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
  2. this.readerContext = readerContext;
  3. // 提取root
  4. // 将注册工作交给doRegisterBeanDefinitions()方法
  5. doRegisterBeanDefinitions(doc.getDocumentElement());
  6. }

接着,我们看doRegisterBeanDefinitions()方法是如何真正的解析XML文件的

  1. protected void doRegisterBeanDefinitions(Element root) {
  2. // xml文件解析器
  3. BeanDefinitionParserDelegate parent = this.delegate;
  4. this.delegate = createDelegate(getReaderContext(), root, parent);
  5. if (this.delegate.isDefaultNamespace(root)) {
  6. // 处理profile属性
  7. String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
  8. if (StringUtils.hasText(profileSpec)) {
  9. String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
  10. profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
  11. // We cannot use Profiles.of(...) since profile expressions are not supported
  12. // in XML config. See SPR-12458 for details.
  13. if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
  14. if (logger.isDebugEnabled()) {
  15. logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
  16. "] not matching: " + getReaderContext().getResource());
  17. }
  18. return;
  19. }
  20. }
  21. }
  22. // 解析前处理,留给子类实现
  23. preProcessXml(root);
  24. // 解析标签
  25. parseBeanDefinitions(root, this.delegate);
  26. // 解析后处理,留给子类实现
  27. postProcessXml(root);
  28. this.delegate = parent;
  29. }

5.1. profile 属性的使用

在注册bean时,最开始解析的是对PROFILE_ATTRIBUTE解析,关于profile属性的作用是用来区分环境的,比如:dev、sit、sandbox等环境
有了profile这个属性,我们就可以同时在配置文件中部署多套配置,来适应不同的环境,如开发环境和生产环境。方便我们在开发、部署时的切换

5.2. 解析并注册BeanDefinition

处理完profile属性后,就可以进行XML文件的解析,parseBeanDefinitions()是我们解析XML文件的方法

  1. protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
  2. // 判断是否有默认命名空间
  3. // 判断是否默认命名空间还是自定义命名空间的办法,如果和http://www.springframework.org/schema/beans相等就表示默认命名空间,否则是自定义命名空间
  4. if (delegate.isDefaultNamespace(root)) {
  5. NodeList nl = root.getChildNodes();
  6. for (int i = 0; i < nl.getLength(); i++) {
  7. Node node = nl.item(i);
  8. if (node instanceof Element) {
  9. Element ele = (Element) node;
  10. // 默认标签解析
  11. // 对根节点或者子节点采用默认命名空间的解析
  12. if (delegate.isDefaultNamespace(ele)) {
  13. parseDefaultElement(ele, delegate);
  14. }
  15. else {
  16. // 自定义命名空间解析
  17. delegate.parseCustomElement(ele);
  18. }
  19. }
  20. }
  21. }
  22. else {
  23. // 自定义命名空间的解析
  24. delegate.parseCustomElement(root);
  25. }
  26. }

6. 默认标签的解析

在Spring中有默认标签和自定义标签两种,两种标签的解析存在这很大的不同。接下来我们先看默认标签是如何解析的。在5.2中默认标签的解析会交给parseDefaultElement()方法来处理,可以发现会解析4种标签,分别有不同的处理方式

  1. private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
  2. // 对import标签的处理
  3. if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
  4. importBeanDefinitionResource(ele);
  5. }
  6. // 对alias标签的处理
  7. else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
  8. processAliasRegistration(ele);
  9. }
  10. // 对bean标签的处理
  11. else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
  12. processBeanDefinition(ele, delegate);
  13. }
  14. // 对beans标签的处理
  15. else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
  16. // recurse
  17. doRegisterBeanDefinitions(ele);
  18. }
  19. }

6.1. import标签的解析

import标签的作用是在xml文件中引入其他的xml文件,这在我们分模块的开发当中是很常见的,将多个模块的bean按模块配置在不同的xml文件中,然后通过import引入这些xml文件。import 标签的解析由importBeanDefinitionResource()方法来完成

  1. protected void importBeanDefinitionResource(Element ele) {
  2. // 获取resource属性
  3. String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
  4. // 如果resource属性不存在,不做任何处理
  5. if (!StringUtils.hasText(location)) {
  6. getReaderContext().error("Resource location must not be empty", ele);
  7. return;
  8. }
  9. // 解析系统属性,如:"${user.dir}"
  10. location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);
  11. Set<Resource> actualResources = new LinkedHashSet<>(4);
  12. // 校验location是绝对URI还是相对URI
  13. boolean absoluteLocation = false;
  14. try {
  15. absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
  16. }
  17. catch (URISyntaxException ex) {
  18. // cannot convert to an URI, considering the location relative
  19. // unless it is the well-known Spring prefix "classpath*:"
  20. }
  21. // 如果是绝对URI就直接根据地址加载对应的配置文件
  22. if (absoluteLocation) {
  23. try {
  24. int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
  25. if (logger.isTraceEnabled()) {
  26. logger.trace("Imported " + importCount + " bean definitions from URL location [" + location + "]");
  27. }
  28. }
  29. catch (BeanDefinitionStoreException ex) {
  30. getReaderContext().error(
  31. "Failed to import bean definitions from URL location [" + location + "]", ele, ex);
  32. }
  33. }
  34. else {
  35. // 相对路径根据相对地址计算出相对地址
  36. try {
  37. int importCount;
  38. //Resource存在多个子实现时,如VfsResource、FileSystemResource等
  39. // 而每个resource的createRelative 的实现方式是不一样的,这里先使用子类的方法尝试解析
  40. Resource relativeResource = getReaderContext().getResource().createRelative(location);
  41. if (relativeResource.exists()) {
  42. importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
  43. actualResources.add(relativeResource);
  44. }
  45. else {
  46. // 如果解析不成功,使用默认解析器DefaultNamespaceHandlerResolver进行解析
  47. String baseLocation = getReaderContext().getResource().getURL().toString();
  48. importCount = getReaderContext().getReader().loadBeanDefinitions(
  49. StringUtils.applyRelativePath(baseLocation, location), actualResources);
  50. }
  51. if (logger.isTraceEnabled()) {
  52. logger.trace("Imported " + importCount + " bean definitions from relative location [" + location + "]");
  53. }
  54. }
  55. catch (IOException ex) {
  56. getReaderContext().error("Failed to resolve current resource location", ele, ex);
  57. }
  58. catch (BeanDefinitionStoreException ex) {
  59. getReaderContext().error(
  60. "Failed to import bean definitions from relative location [" + location + "]", ele, ex);
  61. }
  62. }
  63. // 解析后进行监听器激活处理
  64. Resource[] actResArray = actualResources.toArray(new Resource[0]);
  65. getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
  66. }

6.2. alias标签解析

我们在xml文档中对bean的定义除了用id外,还可以使用alias。alias的作用是为bean提供别名,这样就相当于我们为一个bean定义了多个名称,在Spring容器创建完这个bean后,我们使用其中的任何一个名称就可以获取到该bean

  1. protected void processAliasRegistration(Element ele) {
  2. // 获取bean标签中的beanName
  3. String name = ele.getAttribute(NAME_ATTRIBUTE);
  4. // 获取bean标签中的alias
  5. String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
  6. boolean valid = true;
  7. if (!StringUtils.hasText(name)) {
  8. getReaderContext().error("Name must not be empty", ele);
  9. valid = false;
  10. }
  11. if (!StringUtils.hasText(alias)) {
  12. getReaderContext().error("Alias must not be empty", ele);
  13. valid = false;
  14. }
  15. if (valid) {
  16. try {
  17. // 为bean注册alias
  18. getReaderContext().getRegistry().registerAlias(name, alias);
  19. }
  20. catch (Exception ex) {
  21. getReaderContext().error("Failed to register alias '" + alias +
  22. "' for bean with name '" + name + "'", ele, ex);
  23. }
  24. // 别名注册后,通知监听器做相应处理
  25. getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
  26. }
  27. }

6.3. bean标签的解析及注册

processBeanDefinition()方法只干了4件事情

  1. 解析默认标签,交给BeanDefinitionParserDelegate类的parseBeanDefinitionElement()方法去解析
  2. 解析默认标签子节点下的自定义标签,交给BeanDefinitionParserDelegate类的decorateBeanDefinitionIfRequired()方法去解析
  3. 注册BeanDefinition,交给BeanDefinitionReaderUtils类的registerBeanDefinition()方法完成
  4. 发送注册事件,通知相关监听器

这几件事情都不是由它自己本身完成,而是都交给其他类来完成

  1. protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
  2. // 委托BeanDefinitionParserDelegate类进行元素解析,返回BeanDefinitionHolder
  3. // BeanDefinitionHolder中已经包含了我们在bean标签中定义的id、class、name、alias之类的属性
  4. BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
  5. if (bdHolder != null) {
  6. // 解析默认标签的子节点下的自定义属性
  7. bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
  8. try {
  9. // Register the final decorated instance.
  10. // 注册BeanDefinition
  11. BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
  12. }
  13. catch (BeanDefinitionStoreException ex) {
  14. getReaderContext().error("Failed to register bean definition with name '" +
  15. bdHolder.getBeanName() + "'", ele, ex);
  16. }
  17. // 发送注册事件,通知相关监听器
  18. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
  19. }
  20. }

6.3.1. 解析BeanDefinition

我们接着看BeanDefinitionParserDelegate类的parseBeanDefinitionElement()方法,是如何解析bean标签

  1. public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
  2. return parseBeanDefinitionElement(ele, null);
  3. }
  4. public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
  5. // 解析id属性
  6. String id = ele.getAttribute(ID_ATTRIBUTE);
  7. // 解析name属性
  8. String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
  9. // 分割name属性
  10. List<String> aliases = new ArrayList<>();
  11. if (StringUtils.hasLength(nameAttr)) {
  12. String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
  13. aliases.addAll(Arrays.asList(nameArr));
  14. }
  15. String beanName = id;
  16. if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
  17. beanName = aliases.remove(0);
  18. if (logger.isTraceEnabled()) {
  19. logger.trace("No XML 'id' specified - using '" + beanName +
  20. "' as bean name and " + aliases + " as aliases");
  21. }
  22. }
  23. if (containingBean == null) {
  24. checkNameUniqueness(beanName, aliases, ele);
  25. }
  26. // 对其他标签解析,返回GenerBeanDefinition对象
  27. AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
  28. if (beanDefinition != null) {
  29. if (!StringUtils.hasText(beanName)) {
  30. try {
  31. // 如果beanName不存在,那么根据Spring中提供的命名规则为当前bean生成beanName
  32. if (containingBean != null) {
  33. beanName = BeanDefinitionReaderUtils.generateBeanName(
  34. beanDefinition, this.readerContext.getRegistry(), true);
  35. }
  36. else {
  37. beanName = this.readerContext.generateBeanName(beanDefinition);
  38. // Register an alias for the plain bean class name, if still possible,
  39. // if the generator returned the class name plus a suffix.
  40. // This is expected for Spring 1.2/2.0 backwards compatibility.
  41. String beanClassName = beanDefinition.getBeanClassName();
  42. if (beanClassName != null &&
  43. beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
  44. !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
  45. aliases.add(beanClassName);
  46. }
  47. }
  48. if (logger.isTraceEnabled()) {
  49. logger.trace("Neither XML 'id' nor 'name' specified - " +
  50. "using generated bean name [" + beanName + "]");
  51. }
  52. }
  53. catch (Exception ex) {
  54. error(ex.getMessage(), ele);
  55. return null;
  56. }
  57. }
  58. String[] aliasesArray = StringUtils.toStringArray(aliases);
  59. // 将解析后的bean信息封装到BeanDefinitionHolder中
  60. return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
  61. }
  62. return null;
  63. }

对其他标签解析,统一封装成GenericBeanDefinition对象

  1. public AbstractBeanDefinition parseBeanDefinitionElement(
  2. Element ele, String beanName, @Nullable BeanDefinition containingBean) {
  3. this.parseState.push(new BeanEntry(beanName));
  4. String className = null;
  5. // 解析class属性
  6. if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
  7. className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
  8. }
  9. String parent = null;
  10. // 解析parent属性
  11. if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
  12. parent = ele.getAttribute(PARENT_ATTRIBUTE);
  13. }
  14. try {
  15. // 创建用于用于承载属性AbstractBeanDefinition类型的GenericBeanDefinition对象
  16. AbstractBeanDefinition bd = createBeanDefinition(className, parent);
  17. // 硬编码解析默认bean的各种属性
  18. parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
  19. // 提取description
  20. bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
  21. // 解析元数据
  22. parseMetaElements(ele, bd);
  23. // 解析lookup-method属性
  24. parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
  25. // 解析replaced-method属性
  26. parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
  27. // 解析构造函数参数
  28. parseConstructorArgElements(ele, bd);
  29. // 解析property子元素
  30. parsePropertyElements(ele, bd);
  31. // 解析qualifier 子元素
  32. parseQualifierElements(ele, bd);
  33. bd.setResource(this.readerContext.getResource());
  34. bd.setSource(extractSource(ele));
  35. return bd;
  36. }
  37. catch (ClassNotFoundException ex) {
  38. error("Bean class [" + className + "] not found", ele, ex);
  39. }
  40. catch (NoClassDefFoundError err) {
  41. error("Class that bean class [" + className + "] depends on not found", ele, err);
  42. }
  43. catch (Throwable ex) {
  44. error("Unexpected failure during bean definition parsing", ele, ex);
  45. }
  46. finally {
  47. this.parseState.pop();
  48. }
  49. return null;
  50. }

6.3.2. AbstractBeanDefinition属性

对其他标签解析完后,我们就已经完成了XML文档到GenericBeanDefinition的转换,也就是说到这里xml中的所有配置都可以在GenericBeanDefinition 实例中找到对应的属性
GenericBeanDefinition 只是子类实现,大部分的通用属性都保存在了AbstractBeanDefinition中。我们可以通过查看xml中bean的配置属性和AbstractBeanDefinition类中的属性做对比知道属性的含义。

6.3.3. 解析默认标签中的自定义标签元素

回到6.3中,在调用解析默认标签方法后,仅接着就是解析默认标签中的自定义标签。默认标签中的自定义标签是由BeanDefinitionParserDelegate类的decorateBeanDefinitionIfRequired()方法来完成

  1. // 解析标签方法
  2. protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
  3. // 解析默认标签,也就是6.3.1中分析的
  4. BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
  5. if (bdHolder != null) {
  6. // 解析默认标签中的自定义标签
  7. // 是我们这里要介绍的
  8. bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
  9. try {
  10. // 注册BeanDefinition
  11. BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
  12. }
  13. catch (BeanDefinitionStoreException ex) {
  14. getReaderContext().error("Failed to register bean definition with name '" +
  15. bdHolder.getBeanName() + "'", ele, ex);
  16. }
  17. // Send registration event.
  18. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
  19. }
  20. }
  1. public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder originalDef) {
  2. return decorateBeanDefinitionIfRequired(ele, originalDef, null);
  3. }
  4. public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
  5. Element ele, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {
  6. BeanDefinitionHolder finalDefinition = originalDef;
  7. // 遍历所有属性,看看是否由适用于修饰的属性
  8. NamedNodeMap attributes = ele.getAttributes();
  9. for (int i = 0; i < attributes.getLength(); i++) {
  10. Node node = attributes.item(i);
  11. finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
  12. }
  13. // Decorate based on custom nested elements.
  14. // 遍历所有子节点,看看是否有适用于修饰的子元素
  15. NodeList children = ele.getChildNodes();
  16. for (int i = 0; i < children.getLength(); i++) {
  17. Node node = children.item(i);
  18. if (node.getNodeType() == Node.ELEMENT_NODE) {
  19. finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
  20. }
  21. }
  22. return finalDefinition;
  23. }

上面的代码中对元素的所有属性以及子节点进行分别调用,都通过decorateIfRequired()方法解析。首先获取属性或者元素的命名空间,以此来判断该元素或者属性是否适用于自定义标签的解析条件,找出自定义类型所对应的NamespaceHandler进行进一步解析

  1. public BeanDefinitionHolder decorateIfRequired(
  2. Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {
  3. // 自定义标签的命名空间
  4. String namespaceUri = getNamespaceURI(node);
  5. // isDefaultNamespace(namespaceUri) 对非默认标签进行修饰
  6. if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) {
  7. // 根据命名空间找到对应的处理器
  8. NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
  9. if (handler != null) {
  10. // 修饰
  11. BeanDefinitionHolder decorated =
  12. handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
  13. if (decorated != null) {
  14. return decorated;
  15. }
  16. }
  17. else if (namespaceUri.startsWith("http://www.springframework.org/schema/")) {
  18. error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);
  19. }
  20. else {
  21. // A custom namespace, not to be handled by Spring - maybe "xml:...".
  22. if (logger.isDebugEnabled()) {
  23. logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");
  24. }
  25. }
  26. }
  27. return originalDef;
  28. }

6.3.4. 注册解析的BeanDefinition

对bean解析后得到BeanDefinition后,我们需要将它注册到BeanFactory中,通过processBeanDefinition()方法中调用BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry())方法进行注册。我们看registerBeanDefinition()方法的代码,对beanDefinition的注册分成根据beanName和alias注册

  1. public static void registerBeanDefinition(
  2. BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
  3. throws BeanDefinitionStoreException {
  4. // 使用beanName作为唯一key,把BeanDefinition注册到beanFactory中
  5. String beanName = definitionHolder.getBeanName();
  6. registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
  7. // 如果存在别名,注册别名
  8. String[] aliases = definitionHolder.getAliases();
  9. if (aliases != null) {
  10. for (String alias : aliases) {
  11. registry.registerAlias(beanName, alias);
  12. }
  13. }
  14. }

6.3.4.1. 通过beanName注册BeanDefinition

注册bean的本质其实就是将bean的BeanDefinition信息加入到map缓存中,beanName作为map的key,BeanDefinition作为map的value

  1. public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
  2. throws BeanDefinitionStoreException {
  3. Assert.hasText(beanName, "Bean name must not be empty");
  4. Assert.notNull(beanDefinition, "BeanDefinition must not be null");
  5. // 注册前的最后一次校验,这里的校验不同于之前的XML文件校验
  6. // 主要是对于AbstractBeanDefinition属性中的methodOverrides校验
  7. // 校验methodOverrides是否与工厂方法并存或者methodOverrides对应的方法根本不存在
  8. if (beanDefinition instanceof AbstractBeanDefinition) {
  9. try {
  10. ((AbstractBeanDefinition) beanDefinition).validate();
  11. }
  12. catch (BeanDefinitionValidationException ex) {
  13. throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
  14. "Validation of bean definition failed", ex);
  15. }
  16. }
  17. // 获取bean的定义信息BeanDefinition
  18. BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
  19. // beanName已经被注册
  20. if (existingDefinition != null) {
  21. if (!isAllowBeanDefinitionOverriding()) {
  22. throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
  23. }
  24. else if (existingDefinition.getRole() < beanDefinition.getRole()) {
  25. // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
  26. if (logger.isInfoEnabled()) {
  27. logger.info("Overriding user-defined bean definition for bean '" + beanName +
  28. "' with a framework-generated bean definition: replacing [" +
  29. existingDefinition + "] with [" + beanDefinition + "]");
  30. }
  31. }
  32. else if (!beanDefinition.equals(existingDefinition)) {
  33. if (logger.isDebugEnabled()) {
  34. logger.debug("Overriding bean definition for bean '" + beanName +
  35. "' with a different definition: replacing [" + existingDefinition +
  36. "] with [" + beanDefinition + "]");
  37. }
  38. }
  39. else {
  40. if (logger.isTraceEnabled()) {
  41. logger.trace("Overriding bean definition for bean '" + beanName +
  42. "' with an equivalent definition: replacing [" + existingDefinition +
  43. "] with [" + beanDefinition + "]");
  44. }
  45. }
  46. this.beanDefinitionMap.put(beanName, beanDefinition);
  47. }
  48. else { // beanName 没被注册
  49. if (hasBeanCreationStarted()) {
  50. // 加锁,防止beanDefinitionMap同一时间被多个线程修改
  51. synchronized (this.beanDefinitionMap) {
  52. this.beanDefinitionMap.put(beanName, beanDefinition);
  53. List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
  54. updatedDefinitions.addAll(this.beanDefinitionNames);
  55. updatedDefinitions.add(beanName);
  56. this.beanDefinitionNames = updatedDefinitions;
  57. removeManualSingletonName(beanName);
  58. }
  59. }
  60. else {
  61. // bean的定义信息加入map缓存
  62. this.beanDefinitionMap.put(beanName, beanDefinition);
  63. // 记录已经被注册的bean
  64. this.beanDefinitionNames.add(beanName);
  65. // 从手动注册的单实例map缓存中移除bean定义信息
  66. // 如果是直接通过beanFactory工厂对象将对象注册进单实例缓存中,是属于手动注册
  67. // bean的配置中如果有beanName和手动注册的重名,那么删除手动注册
  68. removeManualSingletonName(beanName);
  69. }
  70. this.frozenBeanDefinitionNames = null;
  71. }
  72. // 重置所有beanName对应的缓存
  73. // 清楚解析之前留下的对应的beanName 的缓存
  74. if (existingDefinition != null || containsSingleton(beanName)) {
  75. resetBeanDefinition(beanName);
  76. }
  77. else if (isConfigurationFrozen()) {
  78. clearByTypeCache();
  79. }
  80. }

6.3.4.2. 通过别名注册BeanDefinition

别名注册方法的代码逻辑很清晰

  1. alias 和 beanName 相同时,删除alias
  2. alias覆盖处理,若aliasName已经使用并已经指向了另一beanName则需要用户设置进行处理
  3. alias循环检查。当A->B存在时,若再次出现A->C->B时抛出异常
  4. 注册alias

    1. public void registerAlias(String name, String alias) {
    2. Assert.hasText(name, "'name' must not be empty");
    3. Assert.hasText(alias, "'alias' must not be empty");
    4. // 锁定存放bean别名的Map
    5. // 防止统一时间只允许一个线程操作别名Map
    6. synchronized (this.aliasMap) {
    7. // 如果beanName和别名相同的话,不记录别名,并从map缓存中删除
    8. if (alias.equals(name)) {
    9. this.aliasMap.remove(alias);
    10. if (logger.isDebugEnabled()) {
    11. logger.debug("Alias definition '" + alias + "' ignored since it points to same name");
    12. }
    13. }
    14. else {
    15. // 从别名map缓存中获取beanName信息
    16. String registeredName = this.aliasMap.get(alias);
    17. if (registeredName != null) {
    18. if (registeredName.equals(name)) {
    19. // 别名已经存在,不需要进行处理
    20. return;
    21. }
    22. if (!allowAliasOverriding()) {
    23. throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" +
    24. name + "': It is already registered for name '" + registeredName + "'.");
    25. }
    26. if (logger.isInfoEnabled()) {
    27. logger.info("Overriding alias '" + alias + "' definition for registered name '" +
    28. registeredName + "' with new target name '" + name + "'");
    29. }
    30. }
    31. // 别名循环检查
    32. // 当A->B存在时,若再次出现A->B->C时候者会抛出异常
    33. checkForAliasCircle(name, alias);
    34. // 将别名加入aliasMap缓存
    35. this.aliasMap.put(alias, name);
    36. if (logger.isDebugEnabled()) {
    37. logger.debug("Alias definition '" + alias + "' registered for name '" + name + "'");
    38. }
    39. }
    40. }
    41. }

    6.4. beans标签的解析及注册

    beans标签是一个定义一组bean的标签,可以可以理解为一个beans标签中可以包含多个bean标签。beans标签解析很简单,它是解析代码是复用doRegisterBeanDefinitions()方法。将Element 元素交给该方法执行,递归解析标签。

6.5. ReaderEventListener 监听器

在前面我们对import、alias、bean、beans标签解析完后,会发送一个事件。Spring没有对这个事件有具体的事件,只留了一个空的实现。

  1. // 解析完标签后,发送事件
  2. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));

如果我们在标签解析完后,通过这个事件来做一些业务处理。我们只需要实现ReaderEventListener接口,然后把这个实现类注册进XmlBeanDefinitionReader中。

  1. public class ReaderEventListenerImpl implements ReaderEventListener {
  2. /**
  3. * 解析标签前事件
  4. * @param defaultsDefinition
  5. */
  6. @Override
  7. public void defaultsRegistered(DefaultsDefinition defaultsDefinition) {
  8. }
  9. /**
  10. * bean 解析完成后事件
  11. * @param componentDefinition
  12. */
  13. @Override
  14. public void componentRegistered(ComponentDefinition componentDefinition) {
  15. }
  16. /**
  17. * alias 标签解析完成后事件
  18. * @param aliasDefinition
  19. */
  20. @Override
  21. public void aliasRegistered(AliasDefinition aliasDefinition) {
  22. }
  23. /**
  24. * import 标签解析完成后事件
  25. * @param importDefinition
  26. */
  27. @Override
  28. public void importProcessed(ImportDefinition importDefinition) {
  29. }
  30. }

把实现类注册进XmlBeanDefinitionRead中,就可以实现在import、alias、bean标签解析完成之后监听消息,做出符合业务逻辑的开发

  1. public static void main(String[] args) {
  2. XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(new XmlBeanFactory(new ClassPathResource("beans.xml")));
  3. reader.setEventListener(new ReaderEventListenerImpl());
  4. }
  5. }

7. 自定义标签的解析

前面已经介绍了Spring的默认标签是如何解析的,现在我们分下下对于自定义标签Spring是如何解析的,我们先回顾一下自定义标签的入口是从哪里开始的,在DefaultBeanDefinitionDocumentReader类中的parseBeanDefinition()方法

  1. protected 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. parseDefaultElement(ele, delegate);
  10. }
  11. else {
  12. // 自定义标签解析
  13. delegate.parseCustomElement(ele);
  14. }
  15. }
  16. }
  17. }
  18. else {
  19. // 自定义标签解析
  20. delegate.parseCustomElement(root);
  21. }
  22. }

接着看parseCustomElement()方法是如何解析自定义标签的

  1. public BeanDefinition parseCustomElement(Element ele) {
  2. return parseCustomElement(ele, null);
  3. }
  4. // containingBd 为父类的bean,对顶层元素的解析应设置为null
  5. public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
  6. // 获取对应的命名空间
  7. String namespaceUri = getNamespaceURI(ele);
  8. if (namespaceUri == null) {
  9. return null;
  10. }
  11. // 根据命名空间找到对应的NamespaceHandler
  12. NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
  13. if (handler == null) {
  14. error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
  15. return null;
  16. }
  17. // 调用自定义的NamespaceHaner 进行解析
  18. return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
  19. }

7.1. 获取标签的命名空间

无论是Spring中默认标签还是自定义标签的解析,都是从命名空间开始的。至于如何提取对应元素的命名空间其实并不需要我们亲自去实现,在org.w3c.dom.Node中已经提供了方法供我们直接调用

  1. public String getNamespaceURI(Node node) {
  2. return node.getNamespaceURI();
  3. }

7.2. 提取自定义标签处理器

有了命名空间,我们就可以进行NamespaceHandler的提取了,继续之前parseCustomElement()方法的跟踪,分析NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri),在readerContext初始化的时候其属性namespaceHandlerResolver已经被初始化为DefaultNamespaceHandlerResolver的实例,所以,这里调用resolve方法其实调用的是DefaultNamespaceHandlerResolver类中的方法,我们进入DefaultNamespaceHandlrResolver的resolve()方法进行查看

  1. public NamespaceHandler resolve(String namespaceUri) {
  2. // 获取所有已经配置的handler映射
  3. Map<String, Object> handlerMappings = getHandlerMappings();
  4. // 根据命名空间找到对应的信息
  5. Object handlerOrClassName = handlerMappings.get(namespaceUri);
  6. if (handlerOrClassName == null) {
  7. return null;
  8. }
  9. else if (handlerOrClassName instanceof NamespaceHandler) {
  10. // 已经做过解析的情况,直接从缓存中读取
  11. return (NamespaceHandler) handlerOrClassName;
  12. }
  13. else {
  14. // 没有做过解析,则返回的是类路径
  15. String className = (String) handlerOrClassName;
  16. try {
  17. // 使用反射将类路径转化为类
  18. Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
  19. if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
  20. throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
  21. "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
  22. }
  23. // 初始化类
  24. NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
  25. // 调用自定义的NamespaceHandler的初始化方法
  26. namespaceHandler.init();
  27. // 记录在缓存
  28. handlerMappings.put(namespaceUri, namespaceHandler);
  29. return namespaceHandler;
  30. }
  31. catch (ClassNotFoundException ex) {
  32. throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
  33. "] for namespace [" + namespaceUri + "]", ex);
  34. }
  35. catch (LinkageError err) {
  36. throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
  37. className + "] for namespace [" + namespaceUri + "]", err);
  38. }
  39. }
  40. }

7.3. 标签解析

通过handler.parse(ele, new ParserContext(this.readerContext, this, containingBd))解析标签,进入到NamespaceHandlerSupport类的parse()方法

  1. public BeanDefinition parse(Element element, ParserContext parserContext) {
  2. // 寻找解析器
  3. BeanDefinitionParser parser = findParserForElement(element, parserContext);
  4. // 标签解析
  5. return (parser != null ? parser.parse(element, parserContext) : null);
  6. }

先寻找元素对应的解析器,然后在调用解析器中的parse()方法

  1. private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
  2. // 获取元素名称
  3. // 如:自定义标签:<myname:user> 那么元素名称就是user
  4. String localName = parserContext.getDelegate().getLocalName(element);
  5. // 根据元素名称找到解析器
  6. BeanDefinitionParser parser = this.parsers.get(localName);
  7. if (parser == null) {
  8. parserContext.getReaderContext().fatal(
  9. "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
  10. }
  11. return parser;
  12. }

解析方法parse()

  1. public final BeanDefinition parse(Element element, ParserContext parserContext) {
  2. AbstractBeanDefinition definition = parseInternal(element, parserContext);
  3. if (definition != null && !parserContext.isNested()) {
  4. try {
  5. String id = resolveId(element, definition, parserContext);
  6. if (!StringUtils.hasText(id)) {
  7. parserContext.getReaderContext().error(
  8. "Id is required for element '" + parserContext.getDelegate().getLocalName(element)
  9. + "' when used as a top-level tag", element);
  10. }
  11. String[] aliases = null;
  12. if (shouldParseNameAsAliases()) {
  13. String name = element.getAttribute(NAME_ATTRIBUTE);
  14. if (StringUtils.hasLength(name)) {
  15. aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
  16. }
  17. }
  18. BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
  19. registerBeanDefinition(holder, parserContext.getRegistry());
  20. if (shouldFireEvents()) {
  21. BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
  22. postProcessComponentDefinition(componentDefinition);
  23. parserContext.registerComponent(componentDefinition);
  24. }
  25. }
  26. catch (BeanDefinitionStoreException ex) {
  27. String msg = ex.getMessage();
  28. parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element);
  29. return null;
  30. }
  31. }
  32. return definition;
  33. }

具体的解析方法交给parseInternal()

  1. protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
  2. BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
  3. String parentName = getParentName(element);
  4. if (parentName != null) {
  5. builder.getRawBeanDefinition().setParentName(parentName);
  6. }
  7. // 获取自定义标签中的class
  8. // 此时会调用自定义解析器中的getBeanClass方法
  9. Class<?> beanClass = getBeanClass(element);
  10. if (beanClass != null) {
  11. builder.getRawBeanDefinition().setBeanClass(beanClass);
  12. }
  13. else {
  14. // 若解析器中没有重写getBeanClass()方法,则尝试检查子类是否重写getBeanClassName()方法
  15. String beanClassName = getBeanClassName(element);
  16. if (beanClassName != null) {
  17. builder.getRawBeanDefinition().setBeanClassName(beanClassName);
  18. }
  19. }
  20. builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
  21. BeanDefinition containingBd = parserContext.getContainingBeanDefinition();
  22. if (containingBd != null) {
  23. // 若存在父类,使用父类的scope属性
  24. builder.setScope(containingBd.getScope());
  25. }
  26. if (parserContext.isDefaultLazyInit()) {
  27. // 配置延迟加载
  28. builder.setLazyInit(true);
  29. }
  30. // 调用子类重写的doParse方法进行解析
  31. doParse(element, parserContext, builder);
  32. return builder.getBeanDefinition();
  33. }

对于自定义标签的解析,需要注意的几个点。

  1. 定义XSD文件来规范xml文档的格式
  2. 继承AbstractSingleBeanDefinitionParser 抽象类实现doParse()方法解析自定义标签
  3. 创建自定义Handler继承至NamespaceHandlerSupport将解析器注册到Spring容器