今天来聊一聊 springboot应用启动过程中的一些神操作
众所周知,springboot主启动类在启动的时候会调用如下代码

  1. @SpringBootApplication
  2. public class EmbeddedContainerApplication {
  3. public static void main(String[] args) {
  4. SpringApplication.run(EmbeddedContainerApplication.class, args);
  5. }
  6. }

那么这个 SpringApplication.run(EmbeddedContainerApplication.class, args); 底层下面究竟做了多少骚操作呢?
跟踪起源码:

  1. public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
  2. return new SpringApplication(sources).run(args);
  3. }

我们发现:主启动类的启动代码就做了两件事
第一件:new SpringApplication()创建了一个springApplication对象
第二件:执行run方法,开启应用

本篇文章来聊一聊在new SpringApplication()中究竟干了什么?

代码是基于springboot1.5.10版本的

在SpringApplication的构造方法中执行了以下代码

  1. private final Set<Object> sources = new LinkedHashSet<Object>();
  2. private boolean webEnvironment;
  3. private List<ApplicationContextInitializer<?>> initializers;
  4. private List<ApplicationListener<?>> listeners;
  5. private Class<?> mainApplicationClass;
  6. public SpringApplication(Object... sources) {
  7. initialize(sources);
  8. }
  9. //在initialize方法中初始化写属性
  10. private void initialize(Object[] sources) {
  11. if (sources != null && sources.length > 0) {
  12. //sources中存放的是存放的是传递进来的主启动类的class
  13. this.sources.addAll(Arrays.asList(sources));
  14. }
  15. this.webEnvironment = deduceWebEnvironment();
  16. setInitializers((Collection) getSpringFactoriesInstances(
  17. ApplicationContextInitializer.class));
  18. setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  19. this.mainApplicationClass = deduceMainApplicationClass();
  20. }

initialize()方法主要进行了如下的操作

1.设置主启动类 sources属性

  1. if (sources != null && sources.length > 0) {
  2. this.sources.addAll(Arrays.asList(sources));
  3. }

2.设置当前是否是web环境

只要这两个类有一个不存在,就是 非web环境

  1. private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
  2. "org.springframework.web.context.ConfigurableWebApplicationContext" };
  3. private boolean deduceWebEnvironment() {
  4. for (String className : WEB_ENVIRONMENT_CLASSES) {
  5. if (!ClassUtils.isPresent(className, null)) {
  6. return false;
  7. }
  8. }
  9. return true;
  10. }

3.设置应用的初始化器ApplicationContextInitializer

这一步是重点 ,这里面涉及到spring通过spi的方式来加载需要的类,需要反复阅读其中的源码,体会理解其中需要的含义

  1. setInitializers((Collection) getSpringFactoriesInstances(
  2. ApplicationContextInitializer.class));
  3. //getSpringFactoriesInstances是如何加载所有的ApplicationContextInitializer的类型的呢?
  4. private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
  5. return getSpringFactoriesInstances(type, new Class<?>[] {});
  6. }
  7. private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
  8. Class<?>[] parameterTypes, Object... args) {
  9. ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
  10. // Use names and ensure unique to protect against duplicates
  11. //这个方法会读取一个配置文件中的所有类名 加载进来
  12. Set<String> names = new LinkedHashSet<String>(
  13. SpringFactoriesLoader.loadFactoryNames(type, classLoader));
  14. List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
  15. classLoader, args, names);
  16. AnnotationAwareOrderComparator.sort(instances);
  17. return instances;
  18. }

SpringFactoriesLoader.loadFactoryNames(),见名知意,就是要加载names ,那么要加载那些names呢:根据注释可以得知,要加载某个类型的全类名(根据给定的接口或者抽象类型 加载所有的这个类型的实现类全类名)

  1. /**
  2. * Load the fully qualified class names of factory implementations of the
  3. * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
  4. * class loader.
  5. * @param factoryClass the interface or abstract class representing the factory
  6. * @param classLoader the ClassLoader to use for loading resources; can be
  7. * {@code null} to use the default
  8. * @see #loadFactories
  9. * @throws IllegalArgumentException if an error occurs while loading factory names
  10. */
  11. public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
  12. String factoryClassName = factoryClass.getName();
  13. try {
  14. Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
  15. ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
  16. List<String> result = new ArrayList<String>();
  17. while (urls.hasMoreElements()) {
  18. URL url = urls.nextElement();
  19. Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
  20. String factoryClassNames = properties.getProperty(factoryClassName);
  21. result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
  22. }
  23. return result;
  24. }
  25. catch (IOException ex) {
  26. throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
  27. "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
  28. }
  29. }

首先是:

  1. /**
  2. * The location to look for factories.
  3. * <p>Can be present in multiple JAR files.
  4. */
  5. public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
  6. //该方法是找到所有META-INF/spring.factories的资源
  7. Enumeration<URL> urls = (classLoader != null ?
  8. classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
  9. ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));

META-INF/spring.factories中存在的内容是

  1. # Initializers
  2. org.springframework.context.ApplicationContextInitializer=\
  3. org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
  4. org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
  5. # Application Listeners
  6. org.springframework.context.ApplicationListener=\
  7. org.springframework.boot.autoconfigure.BackgroundPreinitializer
  1. //将每个文件的key value 加载成properties对象
  2. Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
  3. //从properties中取出org.springframework.context.ApplicationContextInitializer这个类型的
  4. String factoryClassNames = properties.getProperty(factoryClassName);
  5. //将取出的结果用逗号分割,放入result中返回
  6. result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));

接下来是createSpringFactoriesInstances方法 ,因为类名都收集到了,接下来就通过反射机制来实例化这些类

  1. private <T> List<T> createSpringFactoriesInstances(Class<T> type,
  2. Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
  3. Set<String> names) {
  4. //names中存放的就是所有相关的类 全类名
  5. List<T> instances = new ArrayList<T>(names.size());
  6. for (String name : names) {
  7. try {
  8. //遍历这些类进行加载
  9. Class<?> instanceClass = ClassUtils.forName(name, classLoader);
  10. //断言是不是ApplicationContextInitializer这个类型的
  11. Assert.isAssignable(type, instanceClass);
  12. //反射获取构造器
  13. Constructor<?> constructor = instanceClass
  14. .getDeclaredConstructor(parameterTypes);
  15. //实例化对象
  16. T instance = (T) BeanUtils.instantiateClass(constructor, args);
  17. //实例化的对象保存在list中返回
  18. instances.add(instance);
  19. }
  20. catch (Throwable ex) {
  21. throw new IllegalArgumentException(
  22. "Cannot instantiate " + type + " : " + name, ex);
  23. }
  24. }
  25. return instances;
  26. }


最后就是将这些ApplicationContextInitializer的对象集合,设置为SpringApplication对象的属性共使用

  1. private List<ApplicationContextInitializer<?>> initializers;
  2. public void setInitializers(
  3. Collection<? extends ApplicationContextInitializer<?>> initializers) {
  4. this.initializers = new ArrayList<ApplicationContextInitializer<?>>();
  5. this.initializers.addAll(initializers);
  6. }

到此,ApplicationContextInitializer已经初始化完毕,至于这个初始化器到底是干什么用的,我们留点悬念,下回分享

4.设置应用的监听器

同样的方式和ApplicationContextInitializer

  1. private List<ApplicationListener<?>> listeners;
  2. setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  3. public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
  4. this.listeners = new ArrayList<ApplicationListener<?>>();
  5. this.listeners.addAll(listeners);
  6. }
  7. 也是利用spring的工厂加载机制 将所有META-INF/spring.factories文件中的
  8. ApplicationListener类型的类加载进来进行实例化
  9. 然后设置到SpringApplication对象的属性中 供后续使用


5.推断主应用类

如何对端谁是主应用类呢

  1. private Class<?> mainApplicationClass;
  2. this.mainApplicationClass = deduceMainApplicationClass();
  3. private Class<?> deduceMainApplicationClass() {
  4. try {
  5. StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
  6. for (StackTraceElement stackTraceElement : stackTrace) {
  7. if ("main".equals(stackTraceElement.getMethodName())) {
  8. return Class.forName(stackTraceElement.getClassName());
  9. }
  10. }
  11. }
  12. catch (ClassNotFoundException ex) {
  13. // Swallow and continue
  14. }
  15. return null;
  16. }

核心是获取到方法调用栈的内容,如图
image.png

判断 调用栈中的含有main的方法,加载对应的类
image.png最后判定出启动类 是主应用类

本章总结 必看:

1.本文主要讲解了springboot启动的第一步 ,在第一步中创建了SpringApplication对象,并且对其进行了初始化,就是为它的一些属性赋值,包括ApplicationContextInitializer,ApplicationListener等
其中ApplicationContextInitializer,ApplicationListener监听器的作用。我们会专门挑一个章节来详细说明,贪多嚼不烂。
2.SpringFactoriesLoader是这部分内容的章节,负责加载所有META-INF/spring.factories文件中的内容,里面的代码可以详细的学习下
看这个类提供的javadoc就知道其作用

  1. /**
  2. *spring框架底层内部 一个通用的工厂加载机制
  3. * General purpose factory loading mechanism for internal use within the framework.
  4. *
  5. * SpringFactoriesLoader这个类通过loadFactories这个方法,根据给定的类型来加载所有的类,这些
  6. * 类型来自jar包中的META-INF/spring.factories文件,并且这个文件的格式必须是properties内容的
  7. * 格式,key是全类名 value是逗号分割的实现类
  8. * 例如 example.MyService=example.MyServiceImpl1,example.MyServiceImpl2
  9. * 其中example.MyService是接口 后面那两个类是实现
  10. * <p>{@code SpringFactoriesLoader} {@linkplain #loadFactories loads} and instantiates
  11. * factories of a given type from {@value #FACTORIES_RESOURCE_LOCATION} files which
  12. * may be present in multiple JAR files in the classpath. The {@code spring.factories}
  13. * file must be in {@link Properties} format, where the key is the fully qualified
  14. * name of the interface or abstract class, and the value is a comma-separated list of
  15. * implementation class names. For example:
  16. *
  17. * <pre class="code">example.MyService=example.MyServiceImpl1,example.MyServiceImpl2</pre>
  18. *
  19. * where {@code example.MyService} is the name of the interface, and {@code MyServiceImpl1}
  20. * and {@code MyServiceImpl2} are two implementations.
  21. public abstract class SpringFactoriesLoader {
  22. /**
  23. * The location to look for factories.
  24. * <p>Can be present in multiple JAR files.
  25. */
  26. public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
  27. }

3.稍微的需要了解下类的加载机制
ClassLoader.loadClass(className)和Class.forName(className)了解下加载机制