我们都知道启动一个SpringBoot项目,只需要运行主类的main方法,这背后启动机制是什么样的,我们来追踪源码具体分析一下:
我们的启动类是这样的:
@SpringBootApplication
public class DemoSpringbootApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoSpringbootApplication.class, args);
}
}
进入到SpringApplication.run方法中,这边调用了一个重载的方法:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
//调用下面的重载方法
return run(new Class[]{primarySource}, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
我们看到最终启动分成两步:
- 创建SpringApplication对象
- 调用run方法
这一篇我们主要分析一下第一步:创建SpringApplication。
创建SpringApplication
创建SpringApplication对象时会调用其构造方法,我们进入其中,这边只保留了部分核心代码,每步注释如下:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
...//省略一些代码
// 将传入的DemoSpringbootApplication启动类放入primarySources中,这样应用就知道主启动类是什么了
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
//判断当前应用环境,NONE,SERVLET,REACTIVE三个其中的一种
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//设置初始化器
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
//设置监听器
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
//确定主配置类
this.mainApplicationClass = this.deduceMainApplicationClass();
}
我们分析一下其中几个步骤:
-
判断当前应用环境
WebApplicationType.deduceFromClasspath()方法从classpath下判断当前SpringBoot应用应该使用哪种环境启动,代码如下:private static final String[] SERVLET_INDICATOR_CLASSES = new String[]{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"}; private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet"; private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler"; private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer"; private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext"; private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext"; static WebApplicationType deduceFromClasspath() { if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) { return REACTIVE;// 如果类路径下有reactive相关类,返回REACTIVE环境 } else { String[] var0 = SERVLET_INDICATOR_CLASSES; int var1 = var0.length; for(int var2 = 0; var2 < var1; ++var2) { String className = var0[var2]; if (!ClassUtils.isPresent(className, (ClassLoader)null)) { return NONE;// 如果类路径下没有servlet相关类,返回NONE环境 } } return SERVLET;// 以上都不是,则返回SERVLET环境 } }
-
设置初始化器
setInitializers方法会将一组类型为 ApplicationContextInitializer 的初始化器放入 SpringApplication 中。这组ApplicationContextInitializer是通过getSpringFactoriesInstances方法获取的,我们进入方法: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; }
loadFactoryNames方法主要功能就是读取类路径下所有jar包下的META-INF/spring.factories文件,然后找到指定类型(我们这边是ApplicationContextInitializer)的所有实现类。
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 { //读取类路径下所有jar包下的META-INF/spring.factories 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); //每个spring.factories配置文件读取到Properties中 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); } } }
我们debug看一下,项目初始化了如下ApplicationContextInitializer:
createSpringFactoriesInstances方法主要功能就是根据上一步找到的类,通过反射创建这些组件的实例,代码如下:private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) { List<T> instances = new ArrayList(names.size()); Iterator var7 = names.iterator(); while(var7.hasNext()) { String name = (String)var7.next(); try { Class<?> instanceClass = ClassUtils.forName(name, classLoader); Assert.isAssignable(type, instanceClass); Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes); //实例化 T instance = BeanUtils.instantiateClass(constructor, args); instances.add(instance); } catch (Throwable var12) { throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, var12); } } return instances; }
-
设置监听器
setListeners方法会将一组类型为 ApplicationListener的初始化器放入 SpringApplication 中,其获取方法同setInitializers,我们不再赘述。通过debug我们看到初始化了如下一些ApplicationListener:
-
确定主配置类
private Class<?> deduceMainApplicationClass() { try { StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); for (StackTraceElement stackTraceElement : stackTrace) { if ("main".equals(stackTraceElement.getMethodName())) { return Class.forName(stackTraceElement.getClassName()); } } } catch (ClassNotFoundException ex) { // Swallow and continue } return null; }
源码很简单,这个方法就是找到我们的主配置类,在这边就是我们main方法所在的类DemoSpringbootApplication 。
至此,第一步创建SpringApplication的流程分析完了,下一篇我们继续分析run方法。