Spring Boot 启动过程:

1. 创建 SpringApplication 对象。

2. 执行对象的 run() 方法。

3. 将 class 变成 beanDefinition。

4. 将 beanDefinition 变成 bean

5. 图解 循环依赖

6. 图解 bean 的生命周期

7. 图解 aop 拦截器链调用



@SpringBootApplication
public class AutoconfigApplication {
	public static void main(String[] args) {
		SpringApplication.run(AutoconfigApplication.class, args);
	}
}

1. 创建 SpringApplication对象

一路调用下来,执行到了 SpringApplication 的构造方法中。

// resourceLoder = null; primarySources = AutoconfigApplication.class;
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    // 应用程序需要一个资源加载器,来加载外部的文件。
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 给应用程序设定主配置类。
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 根据关键字获取当前 SpringApplication 的类型,结果是:SERVLET。
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 设置引导启动器。
    this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
    // 设置初始化器。
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 设置监听器。
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 设置主类。
    this.mainApplicationClass = deduceMainApplicationClass();
}

构造器中做了什么呢?给应用程序的属性设置值。

  • 设置资源加载器。
  • 设定主配置类,最关键的是主配置类上的 @SpringBootApplication 注解,此处先不展开描述。
  • 设置应用程序类型为 SERVLET,表示当前应用程序是内嵌了 servlet 的 web 应用程序。

The application should run as a servlet-based web application and should start an embedded servlet web server.

SERVLET,

  • 设置引导启动器,应用程序在执行后面的 run() 方法时,会创建引导上下文(一个对象),引导启动器要向这个上下文中加载资源。因为 Spring 框架没有默认的引导启动器,并且开发者也没有自定义引导启动器,所以这个属性值是个空 list。
  • 设置初始化器。应用程序在执行 run() 方法时会创建 IOC容器(这是一个 ApplicationContext 对象),初始化器是后面用来向 IOC 容器中加载资源的。
  • 设置监听器。监听器的作用是监控应用程序的运行状态,并且在一些指定状态出现时发布事件,比如:向终端打印启动时的 log。
  • 设置主类。

SpringApplication 启动时开发者如果要做些操作,那么就应该自定义类并实现Bootstrapper.java 接口,如果开发着要在 IOC 容器中干些什么事情,那么应该自定义类并实现 ApplicationContextInitializer.java接口。

2. 引导启动器、初始化器、监听器都是怎么加载进来的。

从上面的代码可以看到,引导启动器、初始化器、监听器的加载过程是一样的,那就看看getSpringFactoriesInstances() 是怎么把这些都加载进来的。 以 ApplicationContextInitializer.class 为例。

// type = ApplicationContextInitializer.class 
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    // 类加载器。
    ClassLoader classLoader = getClassLoader();
    // 获取到所有 ApplicationContextInitializer.class 子类的全限定名。
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 使用全限定名通过反射给每个初始化器创建一个实例。并将所有实例存放到集合中。
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    // 对集合中的初始化器排序。
    AnnotationAwareOrderComparator.sort(instances);
    // 返回所有的初始化器。
    return instances;
}

初始化器就是这样被加载进来了,但只描述到这里,恐怕连自己都说服不了。初始化器们到底放在哪里了?全限定名怎么被拿到的?它们又是怎么反射的?

2.1 初始化器放在哪里?

Spring 默认的初始化配置器都放在 jar 包的 "META-INF/spring.factories" 文件中。书写格式:全限定接口名 = 全限定子类名 ,等号右边的类全都是等号左边接口的实现类。

spring-boot-autoconfigure-2.4.4.jar/MERA-INF/spring.factories 中有 2 个初始化器:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

spring-boot-2.4.4.jar/MERA-INF/spring.factories 中有 5 个初始化器:

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

框架总共默认了 7 个初始化器。不只是默认的初始化器,默认的监听器、过滤器等等都在 MERA-INF/spring.factories 中写着。

2.2 初始化器的全限定名怎么被加载到了内存中?

默认的全限定名是被写到文件中的,而且有那么多种类,为什么这次只唯独拿到了所有初始化器全限定名,而下次又只拿监听器的全限定名?

//factoryType = org.springframework.context.ApplicationContextInitializer
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
 	// 类加载器。
    ClassLoader classLoaderToUse = classLoader;
    if (classLoaderToUse == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }
    // factoryTypeName = org.springframework.context.ApplicationContextInitializer
    String factoryTypeName = factoryType.getName();
    return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());

这行代码很明能看出来,loadSpringFactories 方法使用 类加载器 classLoaderToUse 加载外部文件并且将信息构建成 map 类型,factoryTypeName 作为 key 去 map 中拿信息,如果能拿到就返回,如果 key 不存在就返回 Collections.emptyList()。这就解释了为什么这次只拿初始化器,下次只拿监听器,因为两次传的参数不一样啊。

那再看看 map 是怎么来的,里面都有些啥?

先说结论:classLoader 将所 jar 包中的 spring.factories 文件都加载了进来(我测试了,只有三个)。并将文件中的信息读到了 Map<String, List<String>> result 中,key 是全限定接口名,value 是该接口子类的全限定类名组成的 list,最后将 result 返回。

再看,一次启动就载入了引导启动器,初始化器、监听器,如果每载入一个XXX器,就将spring.factories文件全部加载一次,那也太浪费了。所以源码在这里做了个小优化,第一次全加载并且构建 result,然后将 result 放到 cache 中缓存起来,等第二次再需要加载 XXX器时,先去 cache 中找下有没有 result,如果有了那直接在 result 中通过 key 值拿 values,如果 result 不存在,那再加载全部 spring.factories 文件。

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    // 先判断缓存中有没有 result。
    Map<String, List<String>> result = cache.get(classLoader);
    if (result != null) {
    	// 有,那就直接返回。
        return result;
    }
	// 如果没有,创建个空 map 准备填数据。
    result = new HashMap<>();
    // try块里面的代码就做了一件事情,读全部spring.factories文件,并将其中的信息构建成键值对放到result。
    try {
        Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
        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();
                String[] factoryImplementationNames =
                    StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
                for (String factoryImplementationName : factoryImplementationNames) {
                    result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                        .add(factoryImplementationName.trim());
                }
            }
        }

        // 这里是去重(不同文件中有时会定义相同的接口实现类)
        result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
                          .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
        // 将构建好的 result 放到缓存中。
        cache.put(classLoader, result);
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                                           FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
    // 返回
    return result;
}

打个断点看下 result 里面到底有什么?

从这里可以看出,全部 spring.factories 文件中总共默认了 16 个接口(暂且这么叫吧),默认了 7 个 ApplicationContextInitializer,1 个 ApplicationListener ,0 个 Bootstrapper。

2.3 类的全限定名怎么反射到实例的?

这块代码还是很简单的,大体来看下:

// names 里面存放的都是类的全限定名。
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {
    // 创建 instances 用于存放实例。
    List<T> instances = new ArrayList<>(names.size());
    // 循环拿到每一个全限定名
    for (String name : names) {
        try {
            // 根据全限定名获取到这个类的 class对象。
            Class<?> instanceClass = ClassUtils.forName(name, classLoader);
            Assert.isAssignable(type, instanceClass);
            // 从类对象中拿构造器。
            Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
            // 用构造器创建类的实例。
            T instance = (T) BeanUtils.instantiateClass(constructor, args);
            // 添加到集合中。
            instances.add(instance);
        }
        catch (Throwable ex) {
            throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
        }
    }
    // 返回 instances
    return instances;
}

通过这样的方式,应用程序默认的初始化器、监听器等就被加载进来了。

最后设置了主类,创建 SpringApplication 对象的代码就走完了,要执行 run 方法。