【spring源码】@Import在SpringBoot自动装配技术中的应用

1,073 阅读4分钟

上一篇文章写了spring中的工厂后置处理器(ConfigurationClassPostProcessor)所做的事情:

【Spring源码】工厂后置处理器之ConfigurationClassPostProcessor

其中说到了@Import注解,它可以引入三种类,第一种是普通类,第二种是实现了ImportSelector接口的类,第三种是实现了ImportBeanDefinitionRegistrar接口的类。我准备写2篇文章来分别说一下,引入普通类比较简单,就糅合在本文中了;

也说到了这个注解也应用在了Mybatis框架和SpringBoot的自动装配技术中,今天就先简单聊聊@Import这个注解在SpringBoot中的应用吧。


1.普通类

@Import({Car.class, Person.class})
public class Test {
}

import的类都将加入到spring容器中,ConfigurationClassPostProcessor将它当做普通的类去处理。

2.ImportSelector

先来看看ImportSelector这个接口:

public interface ImportSelector {

   /**
    * Select and return the names of which class(es) should be imported based on
    * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
    * @return the class names, or an empty array if none
    */
   String[] selectImports(AnnotationMetadata importingClassMetadata);

}
  • 参数:AnnotationMetadata,注解信息,这里可以拿到被@Import标注的所有的注解信息
  • 返回值:String[],返回一个字符串数组,String需要是类的全路径名

这个接口到底有什么用呢?来看看spring中的ConfigurationClassPostProcessor是怎么处理的?

processImports(configClass, sourceClass, getImports(sourceClass), true);

进入这个方法,先看处理ImportSelector这部分代码:

if (candidate.isAssignable(ImportSelector.class)) {
   // Candidate class is an ImportSelector -> delegate to it to determine imports
   Class<?> candidateClass = candidate.loadClass();
   
   ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
   ParserStrategyUtils.invokeAwareMethods(
         selector, this.environment, this.resourceLoader, this.registry);
   if (selector instanceof DeferredImportSelector) {
      this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
   }
   else {
     //得到ImportSelector中方法返回的字符串数组
      String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
     //通过类的全路径名,以反射的方式返回SourceClass
      Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
      /**
       * 
       * 递归调用,注解中引入的类中可能还有 @Import 注解
       * 如果里面没有 @Import  ,进入else 执行 processConfigurationClass(candidate.asConfigClass(configClass));
       */
      processImports(configClass, currentSourceClass, importSourceClasses, false);
   }
}
  • 首先去拿到ImportSelector中方法返回的字符串数组(类全路径名);

  • 然后通过类的全路径名,以反射的方式返回SourceClass:

  • SourceClass asSourceClass(@Nullable String className) throws IOException {
       if (className == null) {
          return new SourceClass(Object.class);
       }
       if (className.startsWith("java")) {
          // Never use ASM for core java types
          try {
             return new SourceClass(ClassUtils.forName(className, this.resourceLoader.getClassLoader()));
          }
          catch (ClassNotFoundException ex) {
             throw new NestedIOException("Failed to load class [" + className + "]", ex);
          }
       }
       return new SourceClass(this.metadataReaderFactory.getMetadataReader(className));
    }
    
  • 最后就会将接口返回的类全路径数组,生成对应的beanDefinition,在后续的过程中实例化bean,放到spring容器中。

3.@Import在SpringBoot自动装配技术中的应用

我们在SpringBoot的项目中,常常会引用一些starter包来集成一些工具,比如spring-boot-starter-data-elasticsearch,而这些starter都是应用了自动装配技术,下面就来揭开SpringBoot自动装配技术的面纱。

@SpringBootApplication注解是SpringBoot的核心注解,点进@SpringBootApplication注解,可以看到以下内容:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
		//...
}

看这些名称,EnableAutoConfiguration一看就知道跟自动装配有关!所以再进入@EnableAutoConfiguration注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
		//...
}

看这里就用到了@Import注解,是不是就跟前面的联系起来了!

再看看AutoConfigurationImportSelector这个类,这个是实现了ImportSelector接口的类,所以直接看它的selectImports方法:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
   }
   AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
         .loadMetadata(this.beanClassLoader);
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
         annotationMetadata);
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

(⊙﹏⊙)这啥呀?还是直接到重点的代码片段吧,路径如下:

#getAutoConfigurationEntry()-->#getCandidateConfigurations()-->SpringFactoriesLoader.loadFactoryNames()-->#loadSpringFactories()

这就是最核心的代码块了:

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
   MultiValueMap<String, String> result = cache.get(classLoader);
   if (result != null) {
      return result;
   }

   try {
      Enumeration<URL> urls = (classLoader != null ?
            classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
            ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
      result = new LinkedMultiValueMap<>();
      while (urls.hasMoreElements()) {
         URL url = urls.nextElement();
         UrlResource resource = new UrlResource(url);
         Properties properties = PropertiesLoaderUtils.loadProperties(resource);
         for (Map.Entry<?, ?> entry : properties.entrySet()) {
            String factoryTypeName = ((String) entry.getKey()).trim();
            for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
               result.add(factoryTypeName, factoryImplementationName.trim());
            }
         }
      }
      cache.put(classLoader, result);
      return result;
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
            FACTORIES_RESOURCE_LOCATION + "]", ex);
   }
}

而上面代码块里的FACTORIES_RESOURCE_LOCATION在类的定义如下:

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

看上面的代码大概就知道,Spring会去扫描加载META-INF/spring.factories文件,还不了解spring.factories文件的同学,可能还是很懵逼,我在这里贴一张图:

factories.png

该图对比上面的代码,黄色部分就是Map<String, List<String>>的Key,而绿色的代码就是map的value。这些value都是类的全路径。

这些类的全路径都会被扫描出来,通过ImportSelector接口的selectImports方法返回,然后被Spring以反射的方式生成beanDefinition,再后续的bean实例化过程中实例化出来,然后放到Spring容器中。


到这里,@Import在SpringBoot自动装配技术的应用就介绍完了,本文不仅讲了@Import引入ImportSelector类的原理,还介绍了SpringBoot的自动装配原理,自认写得还是比较清晰的~

所以读懂了Spring源码,基本后面的SpringBoot的源码基本也就水到渠成的懂了。

本文还没有写@Import引入ImportBeanDefinitionRegistrar类和在Mybatis中的应用,因为文章太长了看着也没耐心嘛,所以下篇文章再写,会模拟写一个Mybatis的Demo~