上一篇文章写了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文件的同学,可能还是很懵逼,我在这里贴一张图:
该图对比上面的代码,黄色部分就是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~