likes
comments
collection
share

第7节 Spring源码之 obtainFreshBeanFactory 方法

作者站长头像
站长
· 阅读数 16

第7节 Spring源码之 obtainFreshBeanFactory 方法

refresh()方法是Spring启动的核心方法,而obtainFreshBeanFactory()方法是该流程中的第二个核心方法,它的核心功能可以概括成以下两点:

  • 创建一个BeanFactory对象,即DefaultListableBeanFactory工厂对象
  • 解析xml文件,构造出BeanDefinition对象,将该对象缓存到DefaultListableBeanFactory中的beanDefinitionMap属性中

一、obtainFreshBeanFactory 方法流程

第7节 Spring源码之 obtainFreshBeanFactory 方法   我认为该流程中应重点关注Spring预留的一些扩展点以及xml文件是如何被解析的。在学习这个流程的源码时,我认为对xml文件是如何转化成Document文档对象这部分逻辑应该忽略,应重点关注Document对象转化成BeanDefinition对象的源码逻辑,因此应重点关注如下核心方法:

  • parseDefaultElement方法:xml文件中默认标签解析
  • parseCustomElement方法:xml文件中自定义标签解析

一、Springxml文件命名空间

首先我们看一下application-bean.xml文件头的结构

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:context="http://www.springframework.org/schema/context"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans

    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
     http://www.springframework.org/schema/context
     https://www.springframework.org/schema/context/spring-context.xsd">

</beans>  
  • 对于一个xml文件来说,要解析它时首先要读取xml文件上的命名空间,比如http://www.springframework.org/schema/beans/,命名空间的作用就是去找对应的xsd文件,用xsd文件来校验你的application-bean.xml文件中的属性标签,如下图所示:

第7节 Spring源码之 obtainFreshBeanFactory 方法 什么是xsd文件呢?xsd(XML Schemas Definition)其实它就是定义了xml文件中的一些标签,比如<bean/><beans/>等等

  • 但是由于命名空间是实时访问网络的,当网络异常无法加载xsd文件。Spring 为了防止这种情况的出现,Spring就将xsd文件放到本地,从而避免了从网络加载xsd文件。通常可以在 Spring 项目的 META-INFO/spring.schemas 目录中找到xsd文件的映射目录: 第7节 Spring源码之 obtainFreshBeanFactory 方法

这个映射文件的设置逻辑是在loadBeanDefinitions(beanFactory) 方法中设置的

第7节 Spring源码之 obtainFreshBeanFactory 方法

二、parseDefaultElement方法

该方法是在DefaultBeanDefinitionDocumentReader中定义的,主要作用是解析xml中的默认标签,比如importaliasbeanbeans

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
   if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
      // 解析 import 标签
      importBeanDefinitionResource(ele);
   } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
      // 解析 alias 标签
      processAliasRegistration(ele);
   } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
      // 解析每个 bean 标签
      processBeanDefinition(ele, delegate);
   } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
      // 递归解析 beans 标签
      doRegisterBeanDefinitions(ele);
   }
}

三、parseCustomElement方法

public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
   // 获取自定义标签对应的命名空间
   String namespaceUri = getNamespaceURI(ele);
   if (namespaceUri == null) {
      return null;
   }

   // 加载命名空间解析器,其实就是找到 META-INFO/spring.handlers 文件路径而已
   // 这个动作是在 registerBeanDefinitions 方法中的 createReaderContext(resource) 中实现的
   NamespaceHandlerResolver namespaceHandlerResolver = this.readerContext.getNamespaceHandlerResolver();

   // 根据 NamespaceHandlerResolver(命名空间解析器) 找到对应的 NamespaceHandler(命名空间)
   NamespaceHandler handler = namespaceHandlerResolver.resolve(namespaceUri);
   if (handler == null) {
      error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
      return null;
   }

   // 调用自定义的NamespaceHandler进行解析
   return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

该方法 parseCustomElement 解析自定义标签的过程中需重点关注 namespaceHandlerResolver.resolve(namespaceUri)handler.parse() 方法

  • Spring 默认的自定义标签

(1)context 标签,比如:<context:component-scan/><context:property-placeholder location=""/>

1. resolve 方法

public NamespaceHandler resolve(String namespaceUri) {
   // 获取 META-INF/spring.handlers 中配置好的NamespaceHandler映射
   // 类似这样的 key-value 结构:http://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
   Map<String, Object> handlerMappings = getHandlerMappings();
   Object handlerOrClassName = handlerMappings.get(namespaceUri);
   if (handlerOrClassName == null) {
      return null;
   } else if (handlerOrClassName instanceof NamespaceHandler) {
      return (NamespaceHandler) handlerOrClassName;
   } else {
      
      String className = (String) handlerOrClassName;
      try {
         // 通过类名称将该类的Class对象
         Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
         if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
            throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri + "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
         }

         // 实例化 NamespaceHandler 类
         NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
         // 调用 NamespaceHandler 的 init() 方法,该方法的主要作用就是将 自定义的标签 和 自定义标签逻辑解析器 绑定
         namespaceHandler.init();

         handlerMappings.put(namespaceUri, namespaceHandler);
         return namespaceHandler;

      } catch (ClassNotFoundException ex) {
         throw new FatalBeanException("Could not find NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]", ex);
      } catch (LinkageError err) {
         throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]", err);
      }
   }
}

该方法的主要逻辑是:

  • 通过反射实例化自定义的 NamespaceHandler
  • 调用NamespaceHandlerinit方法,该方法的调用很重要,就是将 自定义的标签名称自定义标签逻辑解析器 绑定,类似这种实现: 第7节 Spring源码之 obtainFreshBeanFactory 方法
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
   this.parsers.put(elementName, parser);
}

registerBeanDefinitionParser方法其实是向 NamespaceHandlerSupportparsers中注入了 BeanDefinitionParser对象

public abstract class NamespaceHandlerSupport implements NamespaceHandler {
   private final Map<String, BeanDefinitionParser> parsers = new HashMap<>();
}   

2. parse 方法

parse 方法是BeanDefinitionParse接口中的方法,作用就是将xml文档中的元素对应的值设置到 BeanDefinition

public interface BeanDefinitionParser {
	@Nullable
	BeanDefinition parse(Element element, ParserContext parserContext);
}

它的具体实现逻辑是在 AbstractBeanDefinitionParser抽象类中实现了 parse方法,该方法中预留了模板方法parseInternal供子类重写

public final BeanDefinition parse(Element element, ParserContext parserContext) {
   //  模板方法
   AbstractBeanDefinition definition = parseInternal(element, parserContext);
   if (definition != null && !parserContext.isNested()) {
      try {
         // 校验自定义标签必须有id属性
         String id = resolveId(element, definition, parserContext);
         if (!StringUtils.hasText(id)) {
            parserContext.getReaderContext().error("Id is required for element '" + parserContext.getDelegate().getLocalName(element) + "' when used as a top-level tag", element);
         }

         String[] aliases = null;
         if (shouldParseNameAsAliases()) {
            String name = element.getAttribute(NAME_ATTRIBUTE);
            if (StringUtils.hasLength(name)) {
               aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
            }
         }

         // 将AbstractBeanDefinition转换为BeanDefinitionHolder并注册
         BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
         registerBeanDefinition(holder, parserContext.getRegistry());

         if (shouldFireEvents()) {
            BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
            postProcessComponentDefinition(componentDefinition);
            parserContext.registerComponent(componentDefinition);
         }
      } catch (BeanDefinitionStoreException ex) {
         String msg = ex.getMessage();
         parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element);
         return null;
      }
   }
   return definition;
}

AbstractSingleBeanDefinitionParser 中重新了 parseInternal方法,在该方法中又调用了模板方法doParse方法,这个方法是写自定义标签解析逻辑功能的核心方法

3. 实现一个自定义xml标签解析流程

通过上面的自定义标签解析流程,我们可以按照这个流程来定义一个自定义标签,让该标签被Spring容器识别

  • 定义一个命名空间解析器,继承NamespaceHandlerSupport抽象类
  • resources目录下创建META-INFO/spring.handlers文件,绑定命名空间key和命名空间解析器对象的关系
  • 创建一个标签属性解析器,继承AbstractSingleBeanDefinitionParser抽象类
  • 创建META-INFO目录下创建xsdspring.schemas文件

项目目录结构如下:

第7节 Spring源码之 obtainFreshBeanFactory 方法

  • 定义实体
public class UserLogin {

   private String id;
   private String username;
   private String password;

   public String getUsername() {
      return username;
   }
   public void setUsername(String username) {
      this.username = username;
   }

   public String getPassword() {
      return password;
   }
   public void setPassword(String password) {
      this.password = password;
   }

   public String getId() {
      return id;
   }
   public void setId(String id) {
      this.id = id;
   }

   @Override
   public String toString() {
      return "UserLogin{" +
            "id='" + id + ''' +
            ", username='" + username + ''' +
            ", password='" + password + ''' +
            '}';
   }
}
  • 定义解析器
// 命名空间解析器
public class UserLoginNamespaceHandler extends NamespaceHandlerSupport {

   @Override
   public void init() {
      // 这里注册的 elementName 就是你的 xsd 文件中 <element name="user"/> 的name属性值
      registerBeanDefinitionParser("user", new UserLoginBeanDefinitionParser());
   }

   // 自定义标签属性解析器
   private static class UserLoginBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

      @Override
      protected Class<?> getBeanClass(Element element) {
         return UserLogin.class;
      }

      @Override
      protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {

         String uId = element.getAttribute("id");
         if (StringUtils.hasLength(uId)) {
            builder.addPropertyValue("id", uId);
         }

         String uName = element.getAttribute("u-name");
         if (StringUtils.hasLength(uName)) {
            builder.addPropertyValue("username", uName);
         }

         String uPassword = element.getAttribute("u-password");
         if (StringUtils.hasLength(uPassword)) {
            builder.addPropertyValue("password", uPassword);
         }
      }
   }
}
  • 定义META-INFO目录下文件 spring.handlers
http://www.springframework.org/schema/custom/sff/userLogin=com.sff.demo.custom.tag.UserLoginNamespaceHandler

spring.schemas

http://www.springframework.org/schema/custom/sff/userLogin.xsd=META-INF/userLogin.xsd

userLogin.xsd

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
      targetNamespace="http://www.springframework.org/schema/custom/sff/userLogin"
      elementFormDefault="qualified">

   <element name="user">
      <complexType>
         <!--这个name是Spring容器使用的,用于 getBean(name) 使用的-->
         <attribute name="id" type="string"/>
         <attribute name="u-name" type="string"/>
         <attribute name="u-password" type="string"/>
      </complexType>
   </element>

</schema>
  • 验证代码 application-custom-bean.xml
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:userLogin="http://www.springframework.org/schema/custom/sff/userLogin"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

       http://www.springframework.org/schema/custom/sff/userLogin
       http://www.springframework.org/schema/custom/sff/userLogin.xsd">

   <!--向Spring容器中注入对象时,一般标签 id 属性必须设置,是它在容器中的唯一标识-->
   <userLogin:user id="A1106" u-name="大飞" u-password="123456"/>

</beans>

第7节 Spring源码之 obtainFreshBeanFactory 方法 这里命名空间地址一定要对应上,不然解析不了自定义标签

踩坑点: What went wrong: A problem occurred evaluating script. assert shortName != key

第7节 Spring源码之 obtainFreshBeanFactory 方法 此时就将 /gradle/docs.gradle 文件中对应行数的代码注释掉,如下图所示:

第7节 Spring源码之 obtainFreshBeanFactory 方法

四、Spring预留的扩展点

customizeBeanFactory 方法扩展,该方法的主要功能

  • 否允许覆盖同名称的不同定义的对象
  • 是否允许循环依赖
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
   // 是否允许覆盖同名称的不同定义的对象
   if (this.allowBeanDefinitionOverriding != null) {
      beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
   }

   // 是否允许bean之间的循环依赖
   if (this.allowCircularReferences != null) {
      beanFactory.setAllowCircularReferences(this.allowCircularReferences);
   }
}

重新 ClassPathXmlApplicationContext ,程序启动时使用自定义的Spring容器

public class AllowBeanXmlApplicationContext extends ClassPathXmlApplicationContext {
   
   public AllowBeanXmlApplicationContext(String... configLocations) throws BeansException {
      super(configLocations);
   }

   @Override
   protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {

      // 允许覆盖同名称的不同定义的对象
      super.setAllowBeanDefinitionOverriding(true);
      // 允许bean之间的循环依赖
      super.setAllowCircularReferences(true);

      super.customizeBeanFactory(beanFactory);
   }
}