SpringBoot源码(一):初始化SpringApplication

SpringBoot源码(一):初始化SpringApplication

 
 

SpringBoot 中文文档位置:

一、springBoot核心功能优势

  1. 独立运行的Spring项目:Spring Boot可以以jar包的形式进行独立的运行,使用:java -jar xx.jar 就可以成功的运行项目,或者在应用项目的主程序中运行main函数即可;
  2. 内嵌的Servlet容器:内嵌容器,使得我们可以执行运行项目的主程序main函数,并让项目的快速运行
  3. 提供starter简化Manen配置:Spring Boot提供了一系列的starter pom用来简化我们的Maven依赖
  4. 自动配置Spring:Spring Boot会根据我们项目中类路径的jar包/类,为jar包的类进行自动配置Bean,这样一来就大大的简化了我们的配置。当然,这只是Spring考虑到的大多数的使用场景,在一些特殊情况,我们还需要自定义自动配置;(约定优于配置)
  5. 应用监控:Spring Boot提供了基于http、ssh、telnet对运行时的项目进行监控;

二、上代码

1、启动类

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

2、Debug进入 SpringApplication.run 方法,最后到了一个构造函数中

/**
 * 静态助手,可用于使用默认设置从指定源运行SpringApplication
 * @param primarySource 要加载的主要源
 * @param args 应用程序参数(通常从Java main方法传递)
 * @return
 */
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class<?>[] { primarySource }, args);
}

/**
 * 静态助手,可用于使用默认设置和用户提供的参数从指定源运行SpringApplication 。
 * @param primarySources
 * @param args
 * @return
 */
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return new SpringApplication(primarySources).run(args);
}

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

进入SpringApplication.java 构造函数:

/**
 * 创建一个新的SpringApplication实例。 应用程序上下文将从指定的主要源加载Bean
 *(有关详细信息,请参见class-level文档。可以在调用run(String...)之前自定义实例。
 */
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    //上一步入参 resourceLoader 为 null
    this.resourceLoader = resourceLoader;
    //断言 参数primarySources 不能是空,debug:此时参数是 启动类 FastExportApplication.class
    Assert.notNull(primarySources, "PrimarySources must not be null");
    //debug  此时 primarySources 得到值 为 FastExportApplication.class
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    //1、判断webApplicationType启动类型,枚举值如下:
    // NONE:顾名思义,什么都没有,正常流程走,启动时不启动额外的web容器,比如Tomcat
    // SERVLET:应用程序应该作为一个基于servlet的web应用程序运行,并且应该启动一个嵌入式servlet web服务器。
    // REACTIVE:应用程序应该作为响应式web应用程序运行,并且应该启动嵌入式reactive web服务器。
    //这个deduceFromClasspath()方法中最重要的功能:是根据 java.lang.Class#forName(String, boolean, ClassLoader) 的加载class功能,
    // 而class加载是依赖jar包是否引起而判断的,所以如果引入了javax.servlet.Servlet的jar,则会启动SERVLET模式,
    // 如果引入的jar是spring-boot-starter-webflux,而且没引入servlet相关的jar,则会启动REACTIVE模式。
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    //2、设置bootstrappers。从配置文件META-INF/spring.factories中获取key为org.springframework.boot.Bootstrapper的值
    this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
    //3、设置所有可用的初始化器。从配置文件META-INF/spring.factories中获取key为org.springframework.context.ApplicationContextInitializer的值
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    //4、设置所有可用的监听器。从配置文件META-INF/spring.factories中获取key为org.springframework.context.ApplicationListener的值
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    //5、推断并设置main方法的定义类
    this.mainApplicationClass = deduceMainApplicationClass();
}

本构造方法,其实主要做了一下几件事:

  1. 推断应用类型是否web
  2. 设置可用的初始化器
  3. 设置可用的监听器
  4. 推荐main方法的类

详细来说:

推断当前应用的类型:

/**
 * An enumeration of possible types of web application.
 * web应用程序可能类型的枚举
 * @author Andy Wilkinson
 * @author Brian Clozel
 * @since 2.0.0
 * 方法不全,直接截取了当前需要的代码
 */
public enum WebApplicationType {

	/**
	 * The application should not run as a web application and should not start an
	 * embedded web server.
         * 正常流程走,启动时不启动额外的web容器
	 */
	NONE,

	/**
	 * The application should run as a servlet-based web application and should start an
	 * embedded servlet web server.
         * 应用程序应该作为一个基于servlet的web应用程序运行,并且应该启动一个嵌入式servlet web服务器。
	 */
	SERVLET,

	/**
	 * The application should run as a reactive web application and should start an
	 * embedded reactive web server.
         * 应用程序应该作为响应式web应用程序运行,并且应该启动嵌入式reactive web服务器。
	 */
	REACTIVE;

	private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
			"org.springframework.web.context.ConfigurableWebApplicationContext" };

	private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";

	private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";

	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;
	}
}
  • 关于 servlet web与Reactive Web 不懂的,请看这个链接

设置可用的初始化器及可用监听

主要的是进入这个getSpringFactoriesInstances()方法中。

位置:org.springframework.boot.SpringApplication#getSpringFactoriesInstances(java.lang.Class<T>)

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();
    // 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;
}

是如何获取的初始化器的名字?进入这个方法SpringFactoriesLoader.loadFactoryNames

/**
 * 使用给定的类加载器从“ META-INF / spring.factories”加载给定类型的工厂实现的标准类名。
 * 从Spring Framework 5.3开始,如果多次发现给定工厂类型的特定实现类名称,则将忽略重复项。
 * @param factoryType  代表工厂的接口或抽象类
 * @param classLoader  用于加载资源的ClassLoader; 可以为null以使用默认值
 * @throws IllegalArgumentException 如果加载工厂名称时发生错
 * @return List 初始化器的集合
 */
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    // debug classLoaderToUse 不是空,上一步 SpringApplication.getSpringFactoriesInstances 方法中已经 getClassLoader()
    ClassLoader classLoaderToUse = classLoader;
    if (classLoaderToUse == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }
    //获取类名,SpringApplication.java 构造函数 传入的参数factoryType 不是空。
    String factoryTypeName = factoryType.getName();
    return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    //从缓存中获取 初始化器集合
    Map<String, List<String>> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }

    result = new HashMap<>();
    try {
        //工厂位置,类开始定义的配置文件的位置:public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
        //spring.factories 只存在 spring-boot-2.4.4.jar、spring-boot-autoconfigure-2.4.4.jar 中
        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());
                }
            }
        }

        // Replace all lists with unmodifiable lists containing unique elements
        //将所有列表替换为包含唯一元素的不可修改列表
        result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
                .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
        //将spring.factories中的值放入缓存中,下次使用时,直接从缓存中获取即可
        cache.put(classLoader, result);
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
    return result;
}

获取的初始化器个数截图:

获取的监听器个数截图:

推断启动类:

private Class<?> deduceMainApplicationClass() {
    try {
        //获取当前运行步骤
        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
        for (StackTraceElement stackTraceElement : stackTrace) {
            if ("main".equals(stackTraceElement.getMethodName())) {
                //获取执行步骤中含有main方法的类
                return Class.forName(stackTraceElement.getClassName());
            }
        }
    }
    catch (ClassNotFoundException ex) {
        // Swallow and continue
    }
    return null;
}

debug 获取的执行的步骤路径:

最后源码图:

 

编辑于 2021-05-14 10:33
源码阅读
Spring Boot
 

文章被以下专栏收录

努力前行中的程序员。。。。
努力前行中的程序员。。。。

推荐阅读

手把手带你剖析 Springboot 启动原理!

手把手带你剖析 Springboot 启动原理!

SpringBoot源码之属性文件加载原理剖析

SpringBoot源码之属性文件加载原理剖析

手把手带你剖析 Springboot 启动原理!

我们开发任何一个Spring Boot项目,都会用到如下的启动类 @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Applicati…

springboot自动配置原理

配置文件能配置的属性参照 https://docs.spring.io/spring-boot/docs/1.5.9.RELEASE/reference/htmlsingle/#common-application-properties 1、自动配置原理: 1)SpringBoot启动的时候加载…

还没有评论