首页 > 编程语言 > Spring通过<import>标签导入外部配置文件
2021
09-16

Spring通过<import>标签导入外部配置文件

示例

我们先来看下配置文件是怎么导入外部配置文件的?先定义一个spring-import配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
   <import resource="spring-config.xml"/>
   <bean id="innerPerson" class="com.john.aop.Person">
   </bean>
</beans>

我们看到里面定义了一个标签并用属性resource声明了导入的资源文件为spring-config.xml。我们再来看下spring-config.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"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
   <bean id="outerPerson" class="com.john.aop.Person">
          <property name="firstName" value="john"/>
            <property name="lastName" value="wonder"/>
   </bean>
   <bean id="ChineseFemaleSinger" class="com.john.beanFactory.Singer" abstract="true" >
      <property name="country" value="中国"/>
      <property name="gender" value="女"/>
   </bean>
</beans>

然后我们可以实例化一个BeanFactory来加载spring-import这个配置文件了。

public static void main(String[] args) {
   ApplicationContext context=new ClassPathXmlApplicationContext("spring-import.xml");
   Person outerPerson=(Person)context.getBean("outerPerson");
   System.out.println(outerPerson);
}

如果没问题的话,我们可以获取到outerPerson这个bean并打印了。

Person [0, john wonder]

原理

我们来通过源码分析下Spring是如何解析import标签并加载这个导入的配置文件的。首先我们到DefaultBeanDefinitionDocumentReader类中看下:

DefaultBeanDefinitionDocumentReader

我们可以看到类里面定义了一个public static final 的IMPORT_ELEMENT变量:

public static final String IMPORT_ELEMENT = "import";

然后我们可以搜索下哪边用到了这个变量,并且定位到parseDefaultElement函数:

parseDefaultElement

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
   //todo  对 import 标签的解析 2020-11-17
   if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
      importBeanDefinitionResource(ele);
   }
}

这里就是我们要找的导入外部配置文件加载Bean定义的源代码,我们再重点看下importBeanDefinitionResource函数:

importBeanDefinitionResource

/**
 * Parse an "import" element and load the bean definitions
 * from the given resource into the bean factory.
 */
protected void importBeanDefinitionResource(Element ele) {
   //// 获取 resource 的属性值
   String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
   //// 为空,直接退出
   if (!StringUtils.hasText(location)) {
      getReaderContext().error("Resource location must not be empty", ele);
      return;
   }
   // Resolve system properties: e.g. "${user.dir}"
   // // 解析系统属性,格式如 :"${user.dir}"
   location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);
   Set<Resource> actualResources = new LinkedHashSet<>(4);
   // Discover whether the location is an absolute or relative URI
   //// 判断 location 是相对路径还是绝对路径
   boolean absoluteLocation = false;
   try {
      //以 classpath*: 或者 classpath: 开头为绝对路径
      absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
   }
   catch (URISyntaxException ex) {
      // cannot convert to an URI, considering the location relative
      // unless it is the well-known Spring prefix "classpath*:"
   }

   // Absolute or relative?
   //绝对路径 也会调用loadBeanDefinitions
   if (absoluteLocation) {
      try {
         int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
         if (logger.isTraceEnabled()) {
            logger.trace("Imported " + importCount + " bean definitions from URL location [" + location + "]");
         }
      }
      catch (BeanDefinitionStoreException ex) {
         getReaderContext().error(
               "Failed to import bean definitions from URL location [" + location + "]", ele, ex);
      }
   }
   else {
      //如果是相对路径,则先计算出绝对路径得到 Resource,然后进行解析
      // No URL -> considering resource location as relative to the current file.
      try {
         int importCount;
         Resource relativeResource = getReaderContext().getResource().createRelative(location);

         //如果相对路径 这个资源存在 那么就加载这个bean 定义
         if (relativeResource.exists()) {
            importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
            actualResources.add(relativeResource);
         }
         else {
            String baseLocation = getReaderContext().getResource().getURL().toString();
            //todo import节点 内部会调用loadBeanDefinitions 操作 2020-10-17
            importCount = getReaderContext().getReader().loadBeanDefinitions(
                  StringUtils.applyRelativePath(baseLocation, location), actualResources);
         }
         if (logger.isTraceEnabled()) {
            logger.trace("Imported " + importCount + " bean definitions from relative location [" + location + "]");
         }
      }
   }
}

我们来解析下这段代码是怎么一个流程:

1.首先通过resource标签解析到它的属性值,并且判读字符串值是否为空。

2.接下来它还会通过当前上下文环境去解析字符串路径里面的占位符,这点我们在之前文章中分析过。

2.接下来判断是否是绝对路径,通过调用ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();来判断,划重点:以 classpath*: 或者 classpath: 开头为绝对路径,或者可以生成一个URI实例就是当作绝对路径,或者也可以URI的isAbsolute来判断

3.如果是绝对路径那么我们通过getReaderContext().getReader()获取到XmlBeanDefinitionReader然后调用它的loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources)函数

4.如果不是绝对路径那么我们尝试生成相对当前资源的路径(这点很重要),再通过loadBeanDefinitions方法来加载这个配置文件中的BeanDefinitions。这里有个细节需要我们注意,就是它为什么要尝试去判断资源是否存在?就是如果存在的话那么直接调用loadBeanDefinitions(Resource resource)方法,也就是说这里肯定是加载单个资源文件,如方法注释所说:

/**
 * Load bean definitions from the specified XML file.
 * @param resource the resource descriptor for the XML file
 * @return the number of bean definitions found
 * @throws BeanDefinitionStoreException in case of loading or parsing errors
 */
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
   //load中去注册BeanDefinition
   return loadBeanDefinitions(new EncodedResource(resource));
}

从指定的xml文件加载Bean定义。如果不存在,那么就跟绝对路径一样会调用loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources)函数,我们来看看这个函数的定义:

/**
 * Load bean definitions from the specified resource location.
 * <p>The location can also be a location pattern, provided that the
 * ResourceLoader of this bean definition reader is a ResourcePatternResolver.
 * @param location the resource location, to be loaded with the ResourceLoader
 * (or ResourcePatternResolver) of this bean definition reader
 * @param actualResources a Set to be filled with the actual Resource objects
 * that have been resolved during the loading process(要填充加载过程中已解析的实际资源对象*的集合). May be {@code null}
 * to indicate that the caller is not interested in those Resource objects.
 */
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {

解释很清楚,这个location是指从指定资源路径加载BeanDefinitions。

总结

从源码可以看出从外部导入配置文件也就是给了通过一个总的配置文件来加载各个单一配置文件扩展的机会。

以上就是Spring通过<import>标签导入外部配置文件的详细内容,更多关于Spring 导入外部配置文件的资料请关注自学编程网其它相关文章!

编程技巧