前言
首先很多人都知道SpringBoot是开发脚手架,能够帮助快速搭建一个项目,只需要在创建项目时勾选相关的依赖就OK了,因为SpringBoot依靠自动装配为我们导入了所有需要的jar包,那么它是怎么自动装配的呢?本文从源码方面带你剖析自动装配原理!
1.自动装配的开始源于启动类的注解@SpringBootApplication
@SpringBootApplication
public class OauthApplication {
public static void main(String[] args) {
SpringApplication.run(OauthApplication.class, args);
}
}
2.我们可以点进去进入到这个注解的详情里,我们可以发现这是一个组合注解,里面包含了很多个注解。其中自动装配主要是依靠@EnableAutoConfiguration这个注解实现的,这也是我们今天需要重点关注的点。
@Target({ElementType.TYPE}) //修饰的这个注解的使用范围,即被描述的注解可以用在哪里。
@Retention(RetentionPolicy.RUNTIME) //修饰的这个注解的保留位置
@Documented //说明该注解将被包含在javadoc中
@Inherited //说明子类可以继承父类中的该注解
@SpringBootConfiguration // SpringBoot的环境配置(可以导入其他配置类,这样可以将多个配置类组合在一起,形成一个完整的配置环境)
@EnableAutoConfiguration // 自动装配注解(重点!!!)
@ComponentScan( // 扫描启动类所在的同级包及其子包。
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class<?>[] exclude() default {};
@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "nameGenerator"
)
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
3.进入@EnableAutoConfiguration这个注解的里面我们可以看到这仍然是一个组合注解,其中最主要两个:
@AutoConfigurationPackage
这个注解的作用就是指定并扩展SpringBoot的扫描范围,使得SpringBoot能够自动加载和管理这些包下的组件,通俗来说就是将扫描主程序类的所在包及其子包下的所有组件批量导入到容器中。
@Import({AutoConfigurationImportSelector.class})
(重点)@Import是Spring注解之一,它的作用是在配置类中导入其他配置类或者普通的Java类,现在这个注解导入了一个AutoConfigurationImportSelector.class的类,这个类也是自动装配的核心。
4.我们进入到AutoConfigurationImportSelector.class这个类里,进入第一眼就看到特别扎眼的一个方法selectImports()
现在我们看一下 selectImports() 这个方法
参数: AnnotationMetadata annotationMetadata
- AnnotationMetadata在Spring框架中主要用于读取和解析Java类上的注解,用于实现诸如依赖注入、组件扫描、自动配置等Spring核心功能。
方法体:
返回值: String类型的数组
从这里就能看出自动装配的核心就在这:
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
接下来进入到getAutoConfigurationEntry()方法中,在这个方法里有一个非常明显的方法getCandidateConfigurations(),见名知意,这个方法的含义是获取候选配置,感觉已经非常接近真相了。
进入getCandidateConfigurations()这个方法中,在这里发现有一个SpringFactoriesLoader.loadFactoryNames(),这是一个类加载器调用了一个叫loadFactoryNames()的方法。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct."); //在META-INF/spring.factories中未找到自动配置类。如果您使用的是自定义打包方式,请确保该文件正确。
return configurations;
}
进入到loadFactoryNames()方法中,把目光锁定到最后一行loadSpringFactories(),从上面的字符串描述里,结合这个方法的名字能大概猜到它的意思,想要读取META-INF/spring.factories文件。
点击进入到loadSpringFactories()方法里,果然这个方法读取了jar包下的META-INF/spring.factories文件
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
Map<String, List<String>> result = new HashMap();
try {
Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Map.Entry<?, ?> entry = (Map.Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
String[] var10 = factoryImplementationNames;
int var11 = factoryImplementationNames.length;
for(int var12 = 0; var12 < var11; ++var12) {
String factoryImplementationName = var10[var12];
((List)result.computeIfAbsent(factoryTypeName, (key) -> {
return new ArrayList();
})).add(factoryImplementationName.trim());
}
}
}
result.replaceAll((factoryType, implementations) -> {
return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
});
cache.put(classLoader, result);
return result;
} catch (IOException var14) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
}
}
}
然后我们找一个jar包看一看这个spring.factories文件到底是什么东西,打开之后在这个文件里是很多键值对key=value,看着这些键值对是不是一切都明白了,没错,key表示配置类或者接口,value表示该配置类或者接口的实现类或者子类。
刚才的那个loadSpringFactories()的方法读取了所有jar包下的spring.factories文件,意味着它已经获取到了需要自动装配的所有配置信息。然后SpringBoot根据这些配置信息进行了去重、排除的操作,最后加载到Spring容器里,这就是SpringBoot自动装配的原理了!