SpringBoot启动流程分析

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

这段代码相信大家一定不陌生了,接下来我们就以这段代码作为开端,来一步步的分析SpringBoot的启动流程。

SpringApplication:

/*
 * 构建出一个新的SpringApplication并运行,创建并刷新一个新的SpringApplication
 */
public static ConfigurableApplicationContext run(Class<?> primarySource,
		String... args) {
	return run(new Class<?>[] { primarySource }, args);
}

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

SpringApplication

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

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    //重载的构造方法,resourceLoader为null
    this.resourceLoader = resourceLoader;
    //判断传入的primarySources是否为空
    Assert.notNull(primarySources, "PrimarySources must not be null");
    //将primarySources保存到LinkedList中,此时的primarySource是BootApplication.class
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    //deduceFromClasspath()这个方法会判断当前类路径下是否有指定的类,从而决定应用程序的类型,类型分三种REACTIVE,NONE,SERVLET。
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    //getSpringFactoriesInstances()这个方法是实现SpringBoot自动配置的基础,是Spring自实现的SPI
    //从META-INF/spring.factoies下加载ApplicationContextInitializer为key的全类名,并实例化
    setInitializers((Collection) getSpringFactoriesInstances(
    		ApplicationContextInitializer.class));
    //同上,加载ApplicationListener为key的全类名
    setListeners((Collection)
    getSpringFactoriesInstances(ApplicationListener.class));
    //获取运行程序的堆栈,看看运行的是哪个类的main方法,继而保存到上下文。
    this.mainApplicationClass = deduceMainApplicationClass();
}

在SpringApplication的构造方法中,调用了两次getSpringFactoriesInstances(Class<?> type),根据传入的类型不同,去加载不同的资源,接下来我们就看一下这个方法是如何做的。

SpringApplication

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防止重复。
	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(type, classLoader)


	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    
    
	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) {
	    //首先会试着在缓存中查询
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
		    //classLoader.getResources是从类路径下开始查找
		    //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<>();
			//迭代获取到的资源
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				//使用当前的url实例化一个UrlResource
				UrlResource resource = new UrlResource(url);
				//加载资源到内存中
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				//遍历当前资源的键值对
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
				    //获取key
					String factoryClassName = ((String) entry.getKey()).trim();
					// StringUtils.commaDelimitedListToStringArray(str) 把传入的字符串按逗号分隔为数组
			    	// 遍历获取key对应的value
					for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
					    //追加到map中。
						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);
		}
	}

注释已经写的很详细了,这段代码的作用就是从类路径META-INF下加载spring.factories文件,并将其读入内存、解析,最后存入缓存中
那这么做有什么用呢?我们看一下spring.factories里到底有什么呢?

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

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
省略部分代码...
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

我们可以看到在SpringApplication的构造器中,分别获取了org.springframework.context.ApplicationContextInitializer和org.springframework.context.ApplicationListener这两个全类名所定义的全限定类名。

然后就是SpringBoot自动配置相关的知识了,在上一篇文章中也有所提及,接下来会重新写一篇着重研究一下SpringBoot的自动配置原理。

接下来我们看看在实例化SpringApplication之后,run方法中做了什么。

run

public class SpringApplication {

    /**
     * Run the Spring application, creating and refreshing a new
     * {@link ApplicationContext}.
     启动Spring上下文,创建并刷行一个新的ApplicationContext
     * @param args the application arguments (usually passed from a Java main method)
     * @return a running {@link ApplicationContext}
     */
    public ConfigurableApplicationContext run(String... args) {
        //启动时间记录
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        //声明一个IOC容器对象,在try-catch中初始化,以便在出现异常的时候可以将IOC作为参数传入。
        ConfigurableApplicationContext context = null;
        //初始异常报告集合
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        //开启headless模式,设置系统数据java.awt.headless的值为true
        configureHeadlessProperty();
        //从spring.factories中获取SpringApplicationRunListener
        SpringApplicationRunListeners listeners = getRunListeners(args);
        //调用所有监听器的starting方法,SpringBoot正在启动
        listeners.starting();
        try {
            //将main方法中传入的args参数,包装成一个ApplicationArguments对象,方便获取
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            //准备上下文的environment对象
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            //配置忽略bean信息
            configureIgnoreBeanInfo(environment);
            //设置启动时的图标
            Banner printedBanner = printBanner(environment);
            //创建上下文
            context = createApplicationContext();
            //从spring.factories中获取key为SpringBootExceptionReporter
            exceptionReporters = getSpringFactoriesInstances(
                    SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            //准备上下文
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            //刷新上下文
            refreshContext(context);
            //刷新完成之后调用,默认空实现,子类覆盖
            afterRefresh(context, applicationArguments);
            //启动结束
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            //回调监听器的started方法
            listeners.started(context);
              // 调用所有ApplicationRunner和CommandLineRunner的run方法
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }
    
        try {
         	// 回调监听器的 listening 方法,SpringBoot 启动完成!
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }
}

至此,SpringBoot的启动流程我们已经大致过了一遍了, 但是还是有很多囫囵吞枣的地方,在接下来的文章中,我们也会逐一分析SpringBoot启动中涉及到的各种技术点。

Spring Boot是一个开源的Java框架,用于构建独立的、可执行的、生产级的Spring应用程序。它提供了一个快速、简单的方式来开发和部署应用程序。而在Spring Boot的启动过程中,有以下几个主要的步骤: 1. 加载启动类:Spring Boot应用程序的启动类通常是一个带有`@SpringBootApplication`注解的Java类。在应用程序启动时,会通过`main`方法加载这个启动类。 2. 创建Spring Application对象:Spring Boot会创建一个`SpringApplication`对象,用于启动应用程序。`SpringApplication`是Spring Boot框架的核心类,它负责管理整个应用程序的生命周期。 3. 解析配置信息:在启动过程中,`SpringApplication`会解析`application.properties`或`application.yaml`文件中的配置信息,并将其加载到Spring环境中。这些配置信息可以用来配置应用程序的各个方面,如数据库连接、日志级别等。 4. 创建并配置Spring容器:Spring Boot使用Spring容器来管理应用程序中的各个Bean。在启动过程中,`SpringApplication`会根据配置信息创建并配置一个Spring容器,该容器负责加载和管理应用程序中的所有Bean。 5. 执行自定义逻辑:在Spring Boot的启动过程中,可以添加自定义的逻辑。例如,可以通过实现`CommandLineRunner`接口来在应用程序启动后执行一些初始化操作。 6. 启动应用程序:完成上述步骤后,`SpringApplication`会启动应用程序,并通过Servlet容器(如Tomcat、Jetty等)监听端口,开始接收和处理HTTP请求。 总体而言,Spring Boot的启动流程是一个通过加载启动类、解析配置信息、创建和配置Spring容器的过程。通过Spring Boot的自动配置和快速启动能力,开发者可以更加方便地构建和部署Spring应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值