SpringBoot深度探究(七)源码探究启动流程之一

56 篇文章 3 订阅
12 篇文章 2 订阅

前言

前面已经罗嗦了三篇【SpringBoot 四】【SpringBoot 五】【SpringBoot 六】博客用来介绍观察者模式的理论以及实现,都是为了本节讲解SpringBoot启动流程的源码做准备的,因为在阅读SpringBoot源码启动流程的时候发现spring公司的大神们在设计SpringBoot的时候,把观察者模式用到了极限。如果不把观察者模式讲清楚,那么读起来SpringBoot的源码会非常的痛苦。那么既然观察者模式已经搞清楚了,下面就开始正式进入SpringBoot的源码探究的过程。但是要说明一点,本篇探究的源码是基于SpringBoot.2.2.x,因为笔者决定编译阅读源码写笔记的时候,2.2.x是最新版。没想到短短半年就更新到了SpringBoot.2.4.x。关于更新的内容,笔者在后续新开一个博客做一个代码对比,但是总体的思路还是没有变。因此本篇对于SpringBoot源码探究的参考价值还是非常大的,尤其是想要读源码,面对庞大的SpringBoot不知从何下手的同学。更多Spring内容进入【Spring解读系列目录】

SpringBoot启动入口

说到SpringBoot启动,相信大家都知道,只需要一个main()方法作为程序的入口,运行起来SpringBoot就会把Spring容器初始化,Tomcat启动,配置Servlet等等一系列的工作都做完。所以探究源码的入口也要从run()方法开始。

public class MyBootStartTest {
   public static void main(String[] args) {
   		SpringApplication.run(MyBootStartTest.class);
   	}
}

run()方法

没啥可以说的,一路点进去。

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
   return run(new Class<?>[] { primarySource }, args);
}

进入第二个run()方法以后,其实可以发现SpringBoot中的run()静态方法一共做了两件事件:1. new 一个SpringApplication的类。2. 执行SpringApplication类的run()方法。

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
   return new SpringApplication(primarySources).run(args);
}

SpringApplication

首先分析SpringApplication(primarySources)构造方法。

public SpringApplication(Class<?>... primarySources) {
   this(null, primarySources);
}

可以看到SpringApplication构造方法也被封装了一层,Spring的封装简直丧心病狂。

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();
   //找出来ApplicationContextInitializer类,并实例化赋值
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   //找出来ApplicationListener类,并实例化赋值
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   //推断方法入口
   this.mainApplicationClass = deduceMainApplicationClass();
}

this()点进去以后发现就四行代码值得分析,其实也就是这四行造就了一个强大的SpringBoot。先从第一行有用的代码开始看起,很多时候SpringBoot用作搭建一个web项目,但是SpringBoot如果不作为一个Web项目启动,就会加载不同的Spring组件,这个是怎么判断的呢?就是在this.webApplicationType = WebApplicationType.deduceFromClasspath()里面做的项目类型的推断和区分,所以先来看SpringBoot如何推断一个项目的类型,也就是deduceFromClasspath()里面的内容。

推断项目类型

private static final String[] SERVLET_INDICATOR_CLASSES = { "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() {
   //首先判断是不是存在WEBFLUX类型的类,然后在判断是不是存在WEBMVC,在判断是不是存在JERSEY
   if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
         && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) { 
      return WebApplicationType.REACTIVE;
   }
   //for循环查找SERVLET相关的包
   for (String className : SERVLET_INDICATOR_CLASSES) {
   		if (!ClassUtils.isPresent(className, null)) {
   			//如果什么都没找到
    		return WebApplicationType.NONE;
      }
   }
   //如果上述都判断过则判断为SERVLET的Web项目
   return WebApplicationType.SERVLET;
}

进入后,可以看到首先判断的就是:这个项目是否是一个Web项目,如何判断?就是根据里面有没有Web项目相关的包。if语句里面的静态字段存的就是完整的类名。那么第一个判断就是:如果项目里存在WEBFLUX,且WEBMVCJERSEY不存在,就判断为REACTIVE的项目,把WebApplicationType 的类型置为REACTIVE。接着经过一个for循环去找SERVLET_INDICATOR_CLASSES里面的内容,SERVLET_INDICATOR_CLASSES是一个字符串数组,存放的是所有Servlet Web类型相关的类名。如果发现SERVLET_INDICATOR_CLASSES一个都没有,则说明这不是一个Web项目,于是判断为NONE意思是这不是一个Web项目。如果上面的条件都不满足,那么就说明是一个Servlet的Web项目,这个值后面还会用到,作为启动需要验证的条件之一。说完这个跳出来继续下一个方法。

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class))

这里面是方法里套方法,先看外面的方法setInitializers(…),就是进行了一个传递。

public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
   //这里把拿出来的值全部存到initializers里面,这里后面还会用到
   this.initializers = new ArrayList<>(initializers);
}

初始化ApplicationContextInitializer

看到外面的的方法里没有什么东西,那么主要的就是getSpringFactoriesInstances(ApplicationContextInitializer.class)方法了。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
   //type:ApplicationContextInitializer.class注意这个type
   return getSpringFactoriesInstances(type, new Class<?>[] {});
}

继续往getSpringFactoriesInstances(type, new Class<?>[] {})里走,注意传进去的类型是ApplicationContextInitializer.class

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
   //参数type:ApplicationContextInitializer.class
   ClassLoader classLoader = getClassLoader(); //首先有一个class loader
   // Use names and ensure unique to protect against duplicates
   //创建一个set用来存放实例化出来的bean的名字,从官方注释看这里明现是想要去重
   Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
   //把这些拿出来的类名进行初始化,最终返回一个实例列表
   List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
   //排序
   AnnotationAwareOrderComparator.sort(instances);
   //返回实例列表instances
   return instances;
}

可以看到这个方法其实也没有什么复杂的,但是十分重要。一句一句的分析,首先构建一个classloader,然后用传进来的typeclassloader一起产生了一个存放名字的set。那么就进入loadFactoryNames()看下到底要读取什么东西。

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
   String factoryTypeName = factoryType.getName();
   return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

继续往loadSpringFactories()里面走。

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
   MultiValueMap<String, String> result = cache.get(classLoader);
   if (result != null) {
      return result;
   }
   try { //在这里拿到资源FACTORIES_RESOURCE_LOCATION的内容
      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);
   }
}

最终我们发现所有需要加载的内容都是从FACTORIES_RESOURCE_LOCATION变量里来的,变量里面存的是一个文件,这个是什么东西呢?

/**
 * The location to look for factories.
 * <p>Can be present in multiple JAR files. 这个变量里存得是一个文件的路径
 */
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

就是Spring项目在打包的时候生成的配置文件,每一个Spring的jar包基本都有,就是下面这个位置的文件。要说明的是这里由于循环的是整个项目,也就是说所有叫做spring.factories的文件都会被拿出来查找,而且官方注释也说了不止一个:Can be present in multiple JAR files.。位置就在每个jar包的这里。
在这里插入图片描述
里面的内容如下,请注意红框部分,第一个是我们本次传进来的type: ApplicationContextInitializer.class。第二个红框就是我们下面一个大方法要说的类型。
在这里插入图片描述

看到这里不难猜出,这部分的内容就是把和type相关的类全部加载进来,记得刚才是一个存放名字的set,那么后一句。

List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);

就是要把他们全部实例化出来,放到一个instances对象里,返回出去。

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
      ClassLoader classLoader, Object[] args, Set<String> names) {
   List<T> instances = new ArrayList<>(names.size());
   for (String name : names) {  //根据names循环创建对象
      try {
         Class<?> instanceClass = ClassUtils.forName(name, classLoader);
         Assert.isAssignable(type, instanceClass);
         //拿到构造方法
         Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes); 
         //反射创建实例
         T instance = (T) BeanUtils.instantiateClass(constructor, args);
         //添加到list里面
         instances.add(instance);
      }
      catch (Throwable ex) {
         throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
      }
   }//返回出去
   return instances;
}

其实可以看到这个方法就是去找到对应的类的构造方法,反射构造出来,再添加到List里面返回出去。并且在setInitializers()方法里面给全局变量this.initializers赋值,以备他用。

最终setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class))这行代码的作用就是:
进行了所有和ApplicationContextInitializer相关的初始化。内部使用getSpringFactoriesInstances(...)方法将项目中所有和ApplicationContextInitializer有关的类名全部拿出来,并全部实例化。然后经过setInitializers(...)方法对this.initializer进行初始化,从搜索spring.factories和调试的结果来看, SpringBoot会在一开始初始化7个ApplicationContextInitializer类。这七个对象会在后面有各自的用途。

列表如下:
org.springframework.context.ApplicationContextInitializer=\   /*注意这一行不是要实例化类名,是标识*/
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\		1
org.springframework.boot.context.ContextIdApplicationContextInitializer,\					2
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\			3
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\		4
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer 			5
----分割,下面的在另一个spring.factories文件里----
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\		6
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener		7

初始化ApplicationListener

那么SpringApplication构造方法里还有另一行初始化的代码。

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

其实这个方法已经没有什么好说的了,因为细心的同学一定已经看到了,其实这两句基本上是一样的,只是这里传递的参数是ApplicationListener.class,也就是说这个方法会把和ApplicationListener相关的类从spring.factories文件中取出来实例化好,然后放到另外一个全局变量listeners里面,以备他用。

public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
   this.listeners = new ArrayList<>(listeners);
}

在行这里进行了所有和ApplicationListener相关的初始化,这里的代码和上面的一样,只不过传递的类不一样而已。这里也就是SpringBoot的观察者模式的Listener初始化,SpringBoot也就是基于这些观察者编程的。这句话结束了,this.listeners这个对象里面就有数据了。

列表如下:
org.springframework.boot.ClearCachesApplicationListener,\						1
org.springframework.boot.builder.ParentContextCloserApplicationListener,\		2
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\		3
org.springframework.boot.context.FileEncodingApplicationListener,\				4
org.springframework.boot.context.config.AnsiOutputApplicationListener,\			5
org.springframework.boot.context.config.ConfigFileApplicationListener,\			6
org.springframework.boot.context.config.DelegatingApplicationListener,\			7
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\	8
org.springframework.boot.context.logging.LoggingApplicationListener,\			9
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener	10
----分割,下面的在另一个文件里----
org.springframework.boot.autoconfigure.BackgroundPreinitializer					11

到此,SpringBoot把ApplicationContextInitializerApplicationListener相关的所有类都new出来了,以备后面使用。这里比较类似SpringMVC一开始把后置处理器全部从jar包里找到并且new出来的步骤。

推断主类

这个构造方法里最后的一个方法deduceMainApplicationClass(),是为了识别我们项目里main()方法的类是什么类型,话句话说就是为了给SpringBoot标识它的启动类在哪个类上。

private Class<?> deduceMainApplicationClass() {
   //Spring在这里做的主要工作就是抛异常,拿到异常栈的信息。循环异常信息,发现哪个里面有main()方法,就判断哪个就是启动类(主类)
   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;
}

总结

本篇介绍了SpringBoot启动时构造方法里做了一些初始化的工作:首先做的就是推断项目类型,然后将所有ApplicationContextInitializerApplicationListener全部实例化出来备用,最后把程序入口main()方法找出来。那么我们下一篇【SpringBoot深度探究(八)源码探究启动流程之二】将会继续追踪:当SpringBoot环境初始化好了以后run()方法里又做了什么内容。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值