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 导入外部配置文件的资料请关注我们其它相关文章!

(0)

相关推荐

  • 如何利用Spring的@Import扩展点与spring进行无缝整合

    利用Spring的@Import扩展与spring进行无缝整合前言BeanFactoryPostProcessor@Import实现POM文件定义数据层Resource(dao)层的扫描注解定义我的数据层Resource使用的注解ArteryResourceImportBeanDefinitionRegistrar实现自定义扫描类ClassPathArteryResourceScanner代理注册工厂ResourceRegistryResouce的代理工厂真正的代理类方法调用类AbstractB

  • spring注解@Import用法详解

    [1]@Import 参数value接收一个Class数组,将你传入的类以全类名作为id加入IOC容器中 ​比较简单,此处不做详细解释 [2]ImportSelector ImportSelector强调的是复用性,使用它需要创建一个类实现ImportSelector接口,实现方法的返回值是字符串数组,也就是需要注入容器中的组件的全类名.id同样也是全类名. ​ 上代码: //自定义逻辑返回需要导入的组件 public class MyImportSelector implements Impo

  • Spring注解解析之@ImportResource

    一.ImportResource 1.1 定义包和类 首先定义一个不会被ComponentScan扫描到的包outpackage,如下: 在该包内创建一个类: package outpackage; import org.springframework.stereotype.Service; @Service public class HelloService1 { public void method1() { System.out.println("class:HelloService1__

  • Spring @Import注解的使用

    认识@Import注解 先看一下源码 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Import { /** * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar} * or regular component classes to impor

  • SpringBoot Import及自定义装配实现方法解析

    Import的注册形式: 1.使用@Import导入一个或者多个类字节对象 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Import { Class<?>[] value(); } 使用时一般在配置类上注解,表示该注解类导入了其他配置 @Configuration @Import({ MyBeanFactoryPostProcessor.class,

  • 详解Spring 注解之@Import 注入的各种花活

    今天来分享一下 pig4cloud 中涉及的 @Import 的注入形式.通过不同形式的注入方式,最大程度使得架构简洁. @Import导入一个组件 来看 EnablePigxDynamicRoute 这个注解,当我们需要开始动态数据源时,只需要在main 方法加上此注解即可. @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(DynamicRouteAut

  • 浅谈Spring中@Import注解的作用和使用

    @Import用来导入@Configuration注解的配置类.声明@Bean注解的bean方法.导入ImportSelector的实现类或导入ImportBeanDefinitionRegistrar的实现类. @Import注解的作用 查看Import注解源码 /** * Indicates one or more {@link Configuration @Configuration} classes to import. * * <p>Provides functionality eq

  • Spring中@Import的各种用法以及ImportAware接口详解

    @Import 注解 @Import注解提供了和XML中<import/>元素等价的功能,实现导入的一个或多个配置类.@Import即可以在类上使用,也可以作为元注解使用. @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Import { /** * {@link Configuration}, {@link ImportSelector}, {@link I

  • 详解SpringBoot开发使用@ImportResource注解影响拦截器

    问题描述 今天在给SpringBoot项目配置拦截器的时候发现怎么都进不到拦截器的方法里面,在搜索引擎上看了无数篇关于配置拦截器的文章都没有找到解决方案. 就在我准备放弃的时候,在 CSDN 上发现了一篇文章,说的是SpringBoot 用了@ImportResource 配置的拦截器就不起作用了.于是我就赶紧到Application启动类看了一眼,果然项目中使用了@ImportResource 注解用于配置系统的参数. 代码如下: 启动类配置 package com.xx.xxx; impor

  • Springboot @Import 详解

    SpringBoot 的 @Import 用于将指定的类实例注入之Spring IOC Container中. 今天抽空在仔细看了下Springboot 关于 @Import 的处理过程, 记下来以后看. 1. @Import 先看Spring对它的注释 (文档贴过来的), 总结下来作用就是和xml配置的 <import />标签作用一样,允许通过它引入 @Configuration 注解的类 (java config), 引入ImportSelector接口(这个比较重要, 因为要通过它去判

随机推荐