介绍!
我们都知道springboot 自从问世以来,一直有个响亮的口号“约定优于配置”,目的是为了什么呢?那肯定是减少软件开发人员在中作中的各种繁琐的配置,想当年生活在ssh,ssm时代的同学,好家伙,一天花一半时间在配置文件上,动不动给你来个稀奇古怪的错误,哪还有时间谈恋爱泡妞,你不加班谁加班?
秉着面试不被面试官吊的情景下,今天给大家分享一下,springboot这么厉害,都不需要咱们做这么多配置了?那它是怎么做到的呢?它配置又是在哪里呢?又是怎么启动的作用等等一系列问号在跟女朋友花前月下的时候,依然会时不时的冒出来。这严重影响了我们程序员的“幸”福生活,为了能让广大的程序员们跟女朋友过上没羞没臊的生活,以及面试不被面试官diao的情况下,今天咱们就一起跟随源码探究下springboot到底是如何做到"约定优于配置"的,系好安全带,老司机带你飙车嗨起来~
首先,咱们些创建一个springboot的项目,版本呢,就用springboot 2.3.4RELEASE.
在这里加个依赖,表示这是个web工程啦!
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
创建好了之后呢,就可以一起来看一下springboot源码探究一下它的启动流程,话不多少,些找到这个应用程序的入口主方法,在上面吧唧一下打个断点。
启动之后,F5进入到run()方法中
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified sources using default settings and user supplied arguments.
* @param primarySources the primary sources to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
到这里会执行new SpringApplication(primarySources) 创建spring应用对象,继续F5往下跟会执行springApplication构造器。
// 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));
// 1. 可能的web应用程序类型的类型。
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 2. 设置初始化应用context
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 3.设置初始化监听
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 4. 推演主程序类
this.mainApplicationClass = deduceMainApplicationClass();
}
很多不为人知的事情都是发生在这个对象初始化的时候,嘿嘿😋,这里咱们都来一一的解密,一层一层扒光它的"衣服"
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
//这里是我们测试web容器
return WebApplicationType.SERVLET;
}
一、推断web应用类型
这段代码是来推断我们的应用是那种web应用程序
public enum WebApplicationType {
/**
* The application should not run as a web application and should not start an
* embedded web server.
*/
NONE, //不是web应用
/**
* The application should run as a servlet-based web application and should start an
* embedded servlet web server.
*/
SERVLET, //servlet容器
/**
* The application should run as a reactive web application and should start an
* embedded reactive web server.
*/
REACTIVE; //反应型web应用(webflux)
当然,我们一开始就加入了web的依赖,所以我们当然是servlet容器呀。
二、初始化应用上下文
在设置初始化应用context的时候,是先执行了‘getSpringFactoriesInstances(ApplicationContextInitializer.class)’方法,参数是ApplicationContextInitializer.class字节码对象。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(
// 加载ApplicationContextInitializer.class类型的类
// 这里传入就是参数 ApplicationContextInitializer.clas
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 实例化加载到的类
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
// 返回
return instances;
}
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
咱们些来看看他是如何加载到这些类的
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 先从缓存中拿
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 去资源路径下加载
public static final String ACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
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 factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.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,通过断点我们可以看到应用程序是加载了以下几个jar下的spring.factores文件。
双击Shift搜索spring.factories可以看到它存在于以下工程中
spring-boot-2.3.4.RELEASE.jar下的spring.factores(截图未完整截取)
spring-boot-autoconfigure-2.3.4.RELEASE.jar下的spring.factores
spring-beans-5.2.9.RELEASE.jar下的spring.factores
从Map中根据org.springframework.context.ApplicationContextInitializer的类型拿到需要的类初始化类,断点进入getOrDefault(factoryClassName, Collections.emptyList());方法
之后就是吧加载到的需要初始化的类进行实例化添加到一个集合中等待备用
三、初始化监听器类
最关键的还是这句
当我们跟进去之后,会发现在初始化监听类的时候和上面初始化应用上下文是一样的代码。唯一不同的是getSpringFactoriesInstances(ApplicationListener.class))传进去的是·ApplicationListener.class所以这里就不再赘述。
四、推演主程序类
也就是这个最关键的代码了
this.mainApplicationClass = deduceMainApplicationClass();
到这里就完成了springboot启动过程中初始化springapplication的过程。
!小结
主要给大家说了下springboot启动过程中初始化SpringApplication的流程,大致分为四个步骤:
- 推演web应用的类型(如果没有加web依赖类型NONE)
- 初始化ApplicationContextInitializer
- 初始化ApplicationListener
- 推演出主程序类
通过这样四个步骤就完成了第一步springapplication的初始化过程。