springboot启动流程、日志分析

springboot启动流程、日志分析

只关注info级别日志

1. 主类启动,通过调用SpringApplication#run(Class<?> primarySource, String... args)方法启动程序

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

2. SpringApplication实例化、并调用run(Class<?>[] primarySources, String[] args)方法启动

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

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    // 创建创建 SpringApplication 对象并启动
    return new SpringApplication(primarySources).run(args);
}

2.1. 创建SpringApplication对象

主要设置应用类型、初始化器、监听器

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

@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}
  • resourceLoader:资源加载器,无默认值,此处即为null,用途参考文章:IoC 之 Spring 统一资源加载策略
  • primarySources:主要的Java Config 类的数组,此处即为服务启动的主类cn.javadog.demo.springboot.AppConfig
  • webApplicationType:Web 应用类型,调用WebApplicationType#deduceFromClasspath()方法,通过classpath推断,此处即为SERVLET
    • 具体类型取决于引入的maven依赖,如果引入的为spring-boot-starter-webflux则类型为REACTIVE,如果引入的为spring-boot-starter-web则类型为SERVLET,都为引入则为null
    • 这个属性,在下面的 createApplicationContext() 方法,将根据它的值(类型),创建不同类型的 ApplicationContext 对象,即 Spring 容器的类型不同。
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
        "org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework." + "web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org." + "springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";

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;
}
  • initializers: ApplicationContextInitializer 数组
    • 通过 getSpringFactoriesInstances(Class<T> type) 方法,进行获得 ApplicationContextInitializer 类型的对象数组,详细的解析,见「2.1.1 getSpringFactoriesInstances」 方法。
    • 当前只在 Spring MVC 的环境下,initializers 属性的结果如下图:
  • listeners:ApplicationListener 数组
    • 也是通过 #getSpringFactoriesInstances(Class type) 方法,进行获得 ApplicationListener 类型的对象数组
    • 当前只在 Spring MVC 的环境下,initializers 属性的结果如下图:
  • mainApplicationClass:程序启动main方法所在的类,此处即为服务启动的主类cn.javadog.demo.springboot.AppConfig
    • 这个 mainApplicationClass 属性,没有什么逻辑上的用途,主要就是用来打印下日志,说明是通过这个类启动 Spring 应用的
private Class<?> deduceMainApplicationClass() {
		try {
			StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
			for (StackTraceElement stackTraceElement : stackTrace) {
				if ("main".equals(stackTraceElement.getMethodName())) {
					return Class.forName(stackTraceElement.getClassName());
				}
			}
		}
		catch (ClassNotFoundException ex) {
			// Swallow and continue
		}
		return null;
	}
2.1.1 META-INF/spring.factories中指定类对应的实例对象
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();
	// 加载指定类型对应的实现类,去重,在 `META-INF/spring.factories` 里的类名的数组
	Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
	// 创建对象,具体实现不深究
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
	// 将对象进行排序,主要是针对类上有`@Order`注解的
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

2.2 运行run(String... args)方法启动spring程序,注意是spring程序!

public ConfigurableApplicationContext run(String... args) {
    // <1> 创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	ConfigurableApplicationContext context = null;
	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
	// <2> 配置 headless 属性
	configureHeadlessProperty();
	// <3> 获得 SpringApplicationRunListener 的数组,并启动监听
	SpringApplicationRunListeners listeners = getRunListeners(args);
	listeners.starting();
	try {
	     // <4> 创建ApplicationArguments对象
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		 // <5> 加载属性配置。执行完成后,所有的 environment 的属性都会加载进来,包括 application.properties 和外部的属性配置。
		ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
		configureIgnoreBeanInfo(environment);
		// <6> 打印 Spring Banner
		Banner printedBanner = printBanner(environment);
		// <7> 创建 Spring 容器。
		context = createApplicationContext();
		// <8> 异常报告器
		exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
				new Class[] { ConfigurableApplicationContext.class }, context);
	    // <9> 主要是调用所有初始化类的 initialize 方法    
		prepareContext(context, environment, listeners, applicationArguments, printedBanner);
		// <10> 初始化 Spring 容器。
		refreshContext(context);
		// <11> 执行 Spring 容器的初始化的后置逻辑。默认实现为空。
		afterRefresh(context, applicationArguments);
		// <12> 停止 StopWatch 统计时长
		stopWatch.stop();
		// <13> 打印 Spring Boot 启动的时长日志。
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
		}
		// <13> 通知 SpringApplicationRunListener 的数组,Spring 容器启动完成。
		listeners.started(context);
		// <14> 调用 ApplicationRunner 或者 CommandLineRunner 的运行方法。
		callRunners(context, applicationArguments);
	}
	catch (Throwable ex) {
	    // <14.1> 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
		handleRunFailure(context, ex, exceptionReporters, listeners);
		throw new IllegalStateException(ex);
	}

    // <15> 通知 SpringApplicationRunListener 的数组,Spring 容器运行中。
	try {
		listeners.running(context);
	}
	catch (Throwable ex) {
	// <15.1> 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
		handleRunFailure(context, ex, exceptionReporters, null);
		throw new IllegalStateException(ex);
	}
	return context;
}
2.2.1 创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长

逻辑比较简单,就是创建StopWatch对象,其内部有个TaskInfo内部类,记录一次任务的持续时间和任务名称,结果放在StopWatch的taskList属性里;本项目持续时间即为Spring容器初始化的时间,任务名称为空字符串

2.2.2 配置headless属性

这个逻辑,可以无视,和AWT(硬件)相关

2.2.3 获得 SpringApplicationRunListener 的数组,并启动监听

实际类型为SpringApplicationRunListener的实现类EventPublishingRunListener,实现 SpringApplicationRunListener、Ordered 接口,将 SpringApplicationRunListener 监听到的事件,转换成对应的 SpringApplicationEvent 事件,发布到监听器。具体作用暂时不深究

2.2.4 创建ApplicationArguments对象

此处的args目前为空数组!可以通过命令行等方式设置参数

2.2.5 加载属性配置

调用prepareEnvironment(...) 方法,加载属性配置。执行完成后,所有的 environment 的属性都会加载进来,包括 application.properties 和外部的属性配置,具体原理不深究,参见:《【死磕 Spring】—— 环境 & 属性:PropertySource、Environment、Profile》

  • 根据 webApplicationType 类型,会创建不同类型的 ConfigurableEnvironment 对象
  • 主要配置environment有两大属性:propertySourcesactiveProfiles
    • propertySources:根据配置的 defaultProperties、或者 JVM 启动参数,作为附加的 PropertySource 属性源,本项目都没有做配置
    • activeProfiles:激活的profile,未指定即为空数组,可以同时指定多个
  • 通知 SpringApplicationRunListener 的数组,环境变量已经准备完成,相关监听器会产生一些 debug 级别日志的日志,不去深究
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
	// Create and configure the environment
	// <1> 创建 ConfigurableEnvironment 对象,并进行配置
	ConfigurableEnvironment environment = getOrCreateEnvironment();
	configureEnvironment(environment, applicationArguments.getSourceArgs());
	// <2> 通知 SpringApplicationRunListener 的数组,环境变量已经准备完成。
	listeners.environmentPrepared(environment);
	// <3> 绑定 environment 到 SpringApplication 上,暂时不太知道用途。
	bindToSpringApplication(environment);
	// <4> 如果非自定义 environment ,则根据条件转换。认情况下,isCustomEnvironment 为 false ,所以会执行这块逻辑。但是,一般情况下,返回的还是 environment 自身,所以可以无视这块逻辑先。
	if (!this.isCustomEnvironment) {
		environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
	}
	// <5> 如果有 attach 到 environment 上的 MutablePropertySources ,则添加到 environment 的 PropertySource 中。
	ConfigurationPropertySources.attach(environment);
	return environment;
}
2.2.6 打印 Spring Banner

打印的第一个日志,样式如下
banner

  • 默认的是org.springframework.boot.SpringBootBanner打印的spring的启动banner
  • 核心逻辑如下,没有自定义的banner就取默认的,可以通过在路径下添加banner.txt或者指定spring.banner.location属性进行更换
private Banner getBanner(Environment environment) {
	Banners banners = new Banners();
	banners.addIfNotNull(getImageBanner(environment));
	banners.addIfNotNull(getTextBanner(environment));
	if (banners.hasAtLeastOneBanner()) {
		return banners;
	}
	if (this.fallbackBanner != null) {
		return this.fallbackBanner;
	}
	return DEFAULT_BANNER;
}
2.2.7 调用createApplicationContext()创建 Spring 容器

具体解析见下方

2.2.8 创建异常报告器

此处exceptionReporters属性如下,不做深究:

2.2.9 调用 prepareContext(...) 方法,主要是调用所有初始化类的 initialize(...) 方法
private void prepareContext(ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    // <1> 设置 context 的 environment 属性
    context.setEnvironment(environment);
    // <2> 设置 context 的一些属性
    postProcessApplicationContext(context);
    // <3> 初始化 ApplicationContextInitializer
    applyInitializers(context);
    // <4> 通知 SpringApplicationRunListener 的数组,Spring 容器准备完成。
    listeners.contextPrepared(context);
    // <5> 打印日志
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // Add boot specific singleton beans
    // <6> 设置 beanFactory 的属性
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    if (beanFactory instanceof DefaultListableBeanFactory) {
        ((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    // Load the sources
    // <7> 加载 BeanDefinition 们
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    load(context, sources.toArray(new Object[0]));
    // <8> 通知 SpringApplicationRunListener 的数组,Spring 容器加载完成。
    listeners.contextLoaded(context);
}
2.2.10 调用 refreshContext(ConfigurableApplicationContext context) 方法,启动(刷新) Spring 容器

具体解析见下方

2.2.11 调用afterRefresh(...) 方法,执行 Spring容器的初始化的后置逻辑。 默认实现为空。代码如下:
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}
2.2.12 停止 StopWatch 统计时长
2.2.13 打印 Spring Boot 启动的时长日志。效果如下:
2.2.14 调用 SpringApplicationRunListeners#started(ConfigurableApplicationContext context) 方法,通知 SpringApplicationRunListener 的数组,Spring 容器启动完成
2.2.15 调用 SpringApplicationRunListeners#running(ConfigurableApplicationContext context) 方法,通知 SpringApplicationRunListener 的数组,Spring 容器运行中
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
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、付费专栏及课程。

余额充值