spring boot源码初探
-
前言
在spring boot出现之前,以spring为主的项目主要是编写配置文件,但是大量的配置文件带来了开发时间的消耗,后来spring boot的出现,以快速为特点,渐渐步入程序员的眼中。至今也用了spring boot来搭载多个项目,发现简易程度大大提高。今天来记录一下,有关spring boot的源码,看看设计者们的思想。 -
版本
-
源码重点体现于每个spring boot项目必有的启动类
@SpringBootApplication()
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(AuthorityCenterApplication.class, args);
}
最重要的是@SpringBootApplication()注解和SpringApplication类的run办法。
- 配置文件spring.factories
- spring boot项目运行首先会加载这个配置文件
spring.factories文件内容是这样的
最终该文件会以一个Map<k,List>的形式出现,既一个键对应多个值的map - 根据Run办法开始分析如何加载配置文件spring.factories
在阅读源码和分析源码,可以利用idea的ctrl b点方法名可跳转
SpringApplication.run(AuthorityCenterApplication.class, args);
- 点击之后跳转到SpringApplication的静态方法run
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
- 我们继续深入,可以看出new了一个SpringApplication类的对象,我们进入构造器函数看看
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
- 调用了当前函数
public SpringApplication(Class<?>... primarySources) {
this((ResourceLoader)null, primarySources);
}
- 进来了SpringApplication的带参构造器函数
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = new HashSet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
看到这里,是不是可以从代码从看到了一个getSpringFactoriesInstances,那离我们加载配置文件的办法也就进了一步。这里一个是配置一个是监听器,我们点击去看看
- getSpringFactoriesInstances
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return this.getSpringFactoriesInstances(type, new Class[0]);
}
- 继续跟踪
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = this.getClassLoader();
Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
在这里,我们发现了由Set集合封装的names是一个叫SpringFactoriesLoader的loadFactoryNames方法返回的集合,我们跨越loadFactoryNames的方法去探究一下
- SpringFactoriesLoader类
这一步,我们来到了SpringFactoriesLoader的类
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
调用了SpringFactoriesLoader的loadSpringFactories方法
- 继续探索loadSpringFactories
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
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()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryImplementationName = var9[var11];
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
在这个方法中,我们发现了我们的目标
源码的代码要逐句分析来说,相对比较困难,但是ClassLoader加载资源的路径我们可以对比,通过这个方法先生成MultiValueMap来封装一开始我们想要的效果
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
最后再将解析后spring.factories的配置文件数据put进cache中,cache将注入上下文之后对后面的作用也起到了很大的作用。
- 这样一来我们就加载了配置文件,还有很多遗漏的地方没说清楚,但只是记录如何调用。当然,关于springboot还有重要的是@SpringBootApplication()的注解,下一次我们再来探究一下。
总结
在学习框架中,基于用的层面在日常编程中可以训练到,但是基于源码探究,却很少人去关注,没事偶尔探究一下源码,可以看看这些优秀框架设计者们的思维,甚至可以从里面学到一些方法再运用在我们的日常编程中。源码的难度也是会有,但是不必看那么细,想想思路,积少成多,一步一步走就行了。