【框架源码】SpringBoot核心源码解读之启动类源码分析_应用程序

首先我们要先带着我们的疑问,spring boot是如何启动应用程序?去分析SpringBoot的启动源码。

我们在新建SpringBoot项目时,核心方法就是主类的run方法。

SpringApplication.run(ArchWebApplication.class, args)

【框架源码】SpringBoot核心源码解读之启动类源码分析_后端_02

我们点击run方法进入到源码中,这块传入的了一个我们当前程序主类的类对象以及主程序参数。

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

再往下走,调用本身的run方法,这里就开始初始化SpringApplication对象啦,然后在调用run方法。初始化SpringApplication对象时也是将主类的类对象传入进去,然后调用run方法,将主程序传进来的参数传进去。

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

我们先来看SpringApplication对象初始化时都做了哪些操作。

同样调用自身的双参构造方法,null为传入的资源加载器。

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

再往下走就到了初始化SpringApplication的核心逻辑啦。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    //首先是将传来的资源加载器进行赋值,当然我们知道这个资源加载器是null
		this.resourceLoader = resourceLoader;
    //然后在进行类对象的判空
		Assert.notNull(primarySources, "PrimarySources must not be null");
    //然后将传进来的类对像的数组转成list在转成set。
    //(我估计这里是为了去重类对象,因为可以穿进来的可变参数有重复的,可变参数实质就是一个数组)。
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    //deduceFromClasspath 方法的目的是用来判断应用是servlet还是reactive应用
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
    //这一步的逻辑是从spring.factories文件中读取 key为ApplicationContextInitializer的类信息,采用反射实例化对象
    //设置上下文信息
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
    //这一步的逻辑同上,也是从spring.factories文件中读取 key为ApplicationListener的类信息,采用反射实例化对象
  	//设置监听器
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    //判断是否为main函数,配置主函数启动类 class信息
		this.mainApplicationClass = deduceMainApplicationClass();
	}

上面是一个创建SpringApplication的整体逻辑,那么我们在具体看一下 WebApplicationType.deduceFromClasspath()里面的逻辑是怎么样的。WebApplicationType本身是一个枚举类。

//一共三种方式返回服务条件的一种	
	static WebApplicationType deduceFromClasspath() {
    //判断当前应用是不是 REACTIVE应用
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
    //判断是不是 非web应用
		for (String className : SERVLET_INDICATOR_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
    //都不是的话返回 web应用方式
		return WebApplicationType.SERVLET;
	}

ClassUtils.isPresent()这个方法的主要作用是通过反射判断相应的类存不存在。

public static boolean isPresent(String className, @Nullable ClassLoader classLoader) {
		try {
			forName(className, classLoader);
			return true;
		}
		catch (IllegalAccessError err) {
			throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" +
					className + "]: " + err.getMessage(), err);
		}
		catch (Throwable ex) {
			// Typically ClassNotFoundException or NoClassDefFoundError...
			return false;
		}
	}

ok,分析完WebApplicationType.deduceFromClasspath(),我们在来看一下getSpringFactoriesInstances()这个方法的核心逻辑。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) {
    //获取ClassLoader
		ClassLoader classLoader = getClassLoader();
		//这一步很重要,重点在于内部是从spring.factories中获取对应的类信息
		Set<String> names = new LinkedHashSet<>(
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    //等到需要加载的类信息之后,通过反射创建对象。
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
    //排序
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

我们来看一下,loadFactoryNames中都干了什么,它这里面的核心就在于加载配置文件,反射实例化对象

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

	//核心逻辑在这个方法
	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		//首先先去判断下Map中是否有值,有值的话,就直接返回,相当于一个本地缓存。
    MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
      //如果Map没有值的话,获取资源目录下的spring.factories文件。加载配置
      //源码中 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<>();
      //下面就是遍历放进map中
			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);
		}
	}

【框架源码】SpringBoot核心源码解读之启动类源码分析_应用程序_03


【框架源码】SpringBoot核心源码解读之启动类源码分析_spring_04

OK,这里关于SpringApplication初始化的操作就已经完成啦,那么下面我们在一下run()方法里都做些什么操作。传进去的是主程序的参数。

【框架源码】SpringBoot核心源码解读之启动类源码分析_spring boot_05

public ConfigurableApplicationContext run(String... args) {
  	//创建StopWatch对象,用于记录服务启动的时间
		StopWatch stopWatch = new StopWatch();
  	//记录服务启动开始时间
		stopWatch.start();
  	//定义应用程序上下文
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
  	//配置运行程序的系统环境,以确保可正确的运行。
		configureHeadlessProperty();
  	//获取在SpringApplication上的所有监听器。
		SpringApplicationRunListeners listeners = getRunListeners(args);
  	//通知所有监听器,启动应用程序
		listeners.starting();
		try {
      //封装应用程序的主程序参数
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
      //准备应用环境,生成环境变量
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			configureIgnoreBeanInfo(environment);
      //打印应用程序的banner
			Banner printedBanner = printBanner(environment);
      //创建应用上下文对象
			context = createApplicationContext();
      //从spring.factories中获取SpringBootExceptionReporter类型的异常解析器。
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
      //用已有的数据准备上下文,为刷新做准备
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
      //启动应用程序上下文,通过refresh实现
			refreshContext(context);
      //上下文刷新后执行一些后置处理
			afterRefresh(context, applicationArguments);
      //记录结束时间
			stopWatch.stop();
      //判断是否需要记录应用程序的启动信息
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
      //通知所有监听器,应用程序已经启动,传递上下文对象和启动时间
			listeners.started(context);
      //运行所有已经注册的runner
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
      //发布应用上下文就绪事件
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

【框架源码】SpringBoot核心源码解读之启动类源码分析_应用程序_06

ok,下面我们来断点调试下springboot启动类的源码执行。

启动SpringBoot应用程序。

【框架源码】SpringBoot核心源码解读之启动类源码分析_后端_07

【框架源码】SpringBoot核心源码解读之启动类源码分析_spring boot_08

【框架源码】SpringBoot核心源码解读之启动类源码分析_java_09

【框架源码】SpringBoot核心源码解读之启动类源码分析_spring boot_10


【框架源码】SpringBoot核心源码解读之启动类源码分析_应用程序_11

后面就是排序实例 ,返回实例。

【框架源码】SpringBoot核心源码解读之启动类源码分析_后端_12


【框架源码】SpringBoot核心源码解读之启动类源码分析_应用程序_13

剩下的就是我们上面画的那些流程啦。

【框架源码】SpringBoot核心源码解读之启动类源码分析_后端_14

【框架源码】SpringBoot核心源码解读之启动类源码分析_后端_15

【框架源码】SpringBoot核心源码解读之启动类源码分析_后端_16


【框架源码】SpringBoot核心源码解读之启动类源码分析_后端_17

ok,至此SpringBoot启动的全流程就已经完成啦,最后在总结一下大体的流程。

  • 初始化SpringApplication,运行SpringApplication的run方法
  • 读取 spring.factories 的多个初始化器和监听器
  • 配置项目中环境变量、jvm配置信息、配置文件信息
  • 预初始化环境,创建环境对象
  • 创建Spring容器对象(ApplicationContext)
  • 调用spring的refresh加载IOC容器、自动配置类,并创建bean等信息
  • 调用很多监听器并传递上下文对象
  • 运行相关runner