第一章 Spring Boot 启动流程(一)—— Spring Cloud技术初探系列

前言

刚开始准备研究Spring Cloud Gateway的时候,由于对Spring Boot不熟悉,看得有些吃力。无奈,只好从最基础的地方开始。

1.1 Spring Boot 启动入口

纵观以往的Spring框架使用方式,框架的入口都封装的很简单,例如Spring MVC 的DispatcherServlet,Spring Framework的ContextLoaderListener。在Spring Boot中也不例外,提供了一个SpringApplication类来负责启动整个应用。一个完整的web应用只需要以下几行代码:

@SpringBootApplication
@RestController
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
    @GetMapping("/hello")
    public String sayHello(){
        return "Hello World";
    }
}

这就是Spring Boot的魅力所在,让我们关注在自己的业务代码,其余重复性的工作交给它来完成就好了。不过由于它的高度封装,对于初学者来说,如果遇到问题可能就会比较迷茫了。

SpringApplication 的静态run方法内部使用传入的Class对象实例化了一个SpringApplication,并使用从main方法传入的入参调用了实例的run方法。如下图所示,下面就从SpringApplication的构造函数和实例的run方法分别来分析。

源码截图

1.2 SpringApplication的构造函数

在这里插入图片描述
在构造方法里面,完成了部分初始化工作。如标注出来的第1步,是推测当前应用的类型。Spring Boot定义了REACTIVE,SERVLET,NONE三种web应用类型。其中REACTIVE类型为Spring 5.0中支持的基于响应式框架的web应用,从而还诞生了一个新的web框架Spring WebFlux,在后续章节中会进行介绍。而SERVLET则是我们所熟悉的了。探测的逻辑也很简单,就是看classpath下有没有“org.springframework.web.reactive.DispatcherHandler” 或者Servlet api以及容器相关的class,然后返回当前程序可能的web类型。类似的探测也会用在Spring Cloud中对各种组件整合的机制中,很好地体现了“约定大于配置”这个思想,在后续的文章中会详细介绍到。相关代码如下:

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;
			}
		}
		return WebApplicationType.SERVLET;
	}

在第2步中的setInitializer则是初始化Spring ApplicationContext的一个扩展,由ApplicationContextInitializer接口定义。至于这个扩展的作用会在后续章节介绍。这里的关键是getSpringFactoriesInstances方法。在方法内部,使用了SpringFactoriesLoader这个Spring框架内部的SPI机制工具类。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<>(
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

loadFactoryNames方法的内部则是加载classpath中所有“META-INF/spring.factories”文件。取Spring Boot本身的spring.factories文件举例:
在这里插入图片描述
里面的内容为一个类全限定名的key后面接着一系列逗号隔开的类名。因此SpringFactoriesLoader.loadFactoryNames(type, classLoader)这个方法的功能就是从这些properties中找出以type的类全限定名为key后面的所有value,并按逗号拆成类名的集合。然后后面的步骤就是依次将这些扩展class加载并且实例化。因此

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

这句话的意思就已经很清楚了。接着的setListeners方法使用了同样的方法加载ApplicationListener扩展。

正因为spring 定义了一套自己的SPI机制,一些扩展是通过SPI发现机制自动加载到框架中的,所以在分析框架的时候,弄清楚这一套原理就显得很必要。

总结

至此,SpringApplication构造函数中做的事情大体上算是分析完了,在最后一个方法deduceMainApplicationClass,主要是探测Spring Boot的主入口程序的class是哪个,这里面的实现有点意思???


版权声明:本文为ystgx原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。