一. 什么是SpringBoot自动装配
SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar 包中的META-INF/spring.factories 文件,将文件中配置的类型信息加载到 Spring 容器,并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装置进 SpringBoot。
通俗来说,在我们没有使用SpringBoot之前,如果我们需要引入一个三方依赖或者组件,需要进行很多的配置才能实现,但是在使用了SpringBoot之后,我们只需要引入一个maven依赖即可完成,比如引入Redis到我们的项目中,就只需要加入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
然后进行一下Redis相关的地址配置即可,这种就是SpringBoot的自动装配机制
我们本篇分析的是自动装配的过程,所以重点只看自动装配相关的代码,关于过程中其他的SpringBoot启动的相关流程,后续会有文章进行分析。
二. 自动装配如何实现的
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Test1Application {
public static void main(String[] args) {
SpringApplication.run(Test1Application.class, args);
}
}
一个普通的SpringBoot启动类,我们来看他的注解@SpringBootApplication :
可以看到@SpringBootApplication 实际上是一个组合注解,去除掉前四个元注解,剩下的
@SpringBootConfiguration 其实就是一个@Configuration 注解@EnableAutoConfiguration 这个注解是自动装配的关键,其中有两个@Import 注解引入了自动装配需要的类:@Import(AutoConfigurationImportSelector.class) 、@Import(AutoConfigurationPackages.Registrar.class) @ComponentScan 定义了包扫描的信息
这三个注解中,@EnableAutoConfiguration 表示开启自动装配,并引入了AutoConfigurationImportSelector 这个自动装配的关键类。接下来我们从SpringApplication.run 开始顺序分析自动装配的过程
三. 自动装配过程解析
从启动类的SpringApplication.run 进入开始分析,回来到这一步:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
这个方法分两步,一个是new SpringApplication 一个是调用new出来对象实例的run 方法,我们先看new SpringApplication :
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
这段代码是创建SpringApplication 的构造方法,我们重点了来看其中的getSpringFactoriesInstances(ApplicationContextInitializer.class) 这一步。至于后面的getSpringFactoriesInstances(ApplicationListener.class) 和前面的一样流程,只不过是从缓存中取筛选数据进行实例化,所以就不再进行第二次分析了。 代码跟进会来到:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
在上面这个方法中,我们首先要清楚的是入参type 是ApplicationContextInitializer.class ,然后重点来看SpringFactoriesLoader.loadFactoryNames(type, classLoader) :
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
这个方法也是分两个步骤,一个是loadSpringFactories(classLoader) 一个是根据入参的类型名称从中获取需要的内容,其实就是map.getOrDefault 方法。我们重点跟进loadSpringFactories(classLoader) :
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);
}
}
上面这个方法其实算是自动装配中的第一个比较重要的步骤,加载所有的META-INF/spring.factories 内容,这里贴出来一个
|