窥探-spring-boot项目启动运行SpringApplication.run()

  • SpringApplication.run
  • SpringApplication的构造函数
  • 具体操作
  • deduceFromClasspath
  • run方法
  • prepareEnvironment配置环境
  • prepareContext方法
  • refreshContext方法
  • 总结


在我们运行的spring的时候,总是会有一个java的程序入口main方法,在main方法中调用SpringApplication.run(),代码如下:

public static void main(String ... args){
        System.out.println("this is a test");
        //我们的主要方法通过调用run委托给Spring Boot的SpringApplication类。 
        //SpringApplication会引导我们的应用程序,并启动Spring,后者反过来又会启动自动配置的Tomcat Web服务器。
        SpringApplication.run(Main.class, args);
    }

现在我们就窥探一下SpringApplication.run()到底做了哪些事情?

SpringApplication.run

代码调试,进入run方法,如下:

//配置spring上下文
//静态方法,可以使用默认设置从指定的源运行SpringApplication
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
	return run(new Class<?>[] { primarySource }, args);
}
//静态方法,可用于使用默认设置和用户提供的参数从指定的源运行SpringApplication
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
	return new SpringApplication(primarySources).run(args);
}

SpringApplication的构造函数

//创建一个新的SpringApplication实例。 应用程序上下文将从指定的主要来源加载Bean SpringApplication文档。可以在调用之前自定义实例。
public SpringApplication(Class<?>... primarySources) {
	this(null, primarySources);
}

具体操作

//由上面的调用关系,ResourceLoader 为空,设置指定的资源
@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));
	//检查是否为web环境,这里有三种:none:不是web SERVLET:标准的servlet web REACTIVE:该应用程序应作为反应式Web应用程序运行,并应启动嵌入式反应式Web服务器。 校验web容器
	this.webApplicationType = WebApplicationType.deduceFromClasspath();
	//初始化applicationContent,
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
	//初始化监听
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	//设置main方法
	this.mainApplicationClass = deduceMainApplicationClass();
}

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
	return getSpringFactoriesInstances(type, new Class<?>[] {});
}
//获得spring.factories中配置的相关实例
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
	//获取类加载器,ResourceLoader不为空,将使用ResourceLoader,否则使用默认的类加载器
	ClassLoader classLoader = getClassLoader();
	//用名称并确保唯一以防止重复
	Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
	//创建SpringFactoriesInstances 
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

//加载相关的spring.factories
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
//加载spring.factories中相关配置的类
//使用给定的类加载器从FACTORIES_RESOURCE_LOCATION加载给定类型的工厂实现的标准类名
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
	MultiValueMap<String, String> result = cache.get(classLoader);
	if (result != null) {
		return result;
	}
	try {
		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();
			UrlResource resource = new UrlResource(url);
			//加载资源属性
			Properties properties = PropertiesLoaderUtils.loadProperties(resource);
			for (Map.Entry<?, ?> entry : properties.entrySet()) {
				String factoryTypeName = ((String) entry.getKey()).trim();
				for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
					result.add(factoryTypeName, factoryImplementationName.trim());
				}
			}
		}
		cache.put(classLoader, result);
		return result;
	}
	catch (IOException ex) {
		throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
	}
}

deduceFromClasspath

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;
}

通过不同的引入java包,判断相关的类是否存在来返回当前spring-boot的应用web容器是什么。
从上面可以看出,SpringApplication创建的时候,会有如下步骤:

  • 设置resourceLoader的类加载器,如果为空的话,将使用默认的类加载器,否则将使用指定的resourceLoader
  • 设置指定的资源文件
  • 判断是否有引入web相关的包
  • 初始化spring factories并创建
  • 指定main应用入口类

讲解了springApplication,再来看看相关的run方法。

run方法

运行spring application,创建和刷新ApplicationContext

public ConfigurableApplicationContext run(String... args) {
	//进行操作时间的记录
	StopWatch stopWatch = new StopWatch();
	//记录开始时间
	stopWatch.start();
	//定义一个ConfigurableApplicationContext,进行applicationContext的设置
	ConfigurableApplicationContext context = null;
	//异常报告集合
	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
	//配置headLess属性,往System.setProperty("java.awt.headless",true);
	configureHeadlessProperty();
	//得到监听
	SpringApplicationRunListeners listeners = getRunListeners(args);
	listeners.starting();
	try {
		//获取相关的启动参数
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		//配置相关运行环境
		ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
		//配置忽略bean的信息
		//environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);之后进行system.setProperty进行设置
		configureIgnoreBeanInfo(environment);
		//进行banner的设置
		//默认为Banner.Mode.CONSOLE,打印banner
		Banner printedBanner = printBanner(environment);
		//创建spring的上下文信息
		//this.applicationContextClass;进行判断
		//如果为null,则进行this.webApplicationType web容器的选择
		context = createApplicationContext();
		//获取SpringFactories关于异常report
		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);
		}
		//监听调用
		listeners.started(context);
		//调用启动application
		callRunners(context, applicationArguments);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, listeners);
		throw new IllegalStateException(ex);
	}
	try {
		//监听运行
		listeners.running(context);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, null);
		throw new IllegalStateException(ex);
	}
	//返回上下文
	return context;
}

prepareEnvironment配置环境

// 创建和配置运行环境
ConfigurableEnvironment environment = getOrCreateEnvironment(); //检查运行环境,如果environment不为空的
//配置环境
configureEnvironment(environment, applicationArguments.getSourceArgs());
//将ConfigurationPropertySource支持附加到指定的Environment。 使环境管理的每个PropertySource适应于ConfigurationPropertySource,并允许经典的 PropertySourcesPropertyResolver调用使用ConfigurationPropertyName配置属性名称进行解析。
//附加的解析器将动态跟踪来自基础Environment属性源的任何添加或删除。
ConfigurationPropertySources.attach(environment);
//监听
listeners.environmentPrepared(environment);
//绑定environment到springApplication
bindToSpringApplication(environment);
//判断客户自定义的配置
if (!this.isCustomEnvironment) {
	environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
			deduceEnvironmentClass());
}
//将ConfigurationPropertySource支持附加到指定的Environment。 使环境管理的每个PropertySource适应于ConfigurationPropertySource,并允许经典的 PropertySourcesPropertyResolver调用使用ConfigurationPropertyName配置属性名称进行解析。
//附加的解析器将动态跟踪来自基础Environment属性源的任何添加或删除。
ConfigurationPropertySources.attach(environment);
return environment;

configureEnvironment方法:

//委托给的模板方法configurePropertySources(ConfigurableEnvironment,String [])
//configureProfiles(ConfigurableEnvironment,String [])按此顺序。
//重写此方法以完全控制环境自定义,或以下一种分别用于对属性源或配置文件进行细粒度控制。
if (this.addConversionService) {
		ConversionService conversionService = ApplicationConversionService.getSharedInstance();
		environment.setConversionService((ConfigurableConversionService) conversionService);
	}
	//添加,移除和重新排序PropertySources在当前的application环境中
	configurePropertySources(environment, args);
	//配置该配置文件环境中哪些配置文件处于活动状态(默认情况下处于活动状态)。 在配置文件处理过程中,可以通过spring.profiles.active属性激活其他配置文件
	configureProfiles(environment, args);
}

spring项目启动如何应用banner 怎么启动spring项目_SpringApplica


spring项目启动如何应用banner 怎么启动spring项目_SpringApplica_02


spring项目启动如何应用banner 怎么启动spring项目_run_03


绑定environment到springApplication:

spring项目启动如何应用banner 怎么启动spring项目_springboot_04

prepareContext方法

//设置运行环境
context.setEnvironment(environment);
//应用任何相关的后处理ApplicationContext 子类可以根据需要应用其他处理
postProcessApplicationContext(context);
//在刷新之前,将所有ApplicationContextInitializer应用于上下文。
applyInitializers(context);
listeners.contextPrepared(context);
//进行启动日志输出
if (this.logStartupInfo) {
	logStartupInfo(context.getParent() == null);
	logStartupProfileInfo(context); //输出active的相关日志
}
// 添加boot特定的单例bean
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
//加载banner
if (printedBanner != null) {
	beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
	((DefaultListableBeanFactory) beanFactory)
	.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
//是否懒加载bean
if (this.lazyInitialization) {
	context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// 加载资源
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
//一旦应用程序上下文已加载但在刷新之前调用
listeners.contextLoaded(context);

加载资源:

spring项目启动如何应用banner 怎么启动spring项目_spring_05

refreshContext方法

刷新context:

spring项目启动如何应用banner 怎么启动spring项目_main_06


spring项目启动如何应用banner 怎么启动spring项目_main_07


applicationCpntext.refresh方法

spring项目启动如何应用banner 怎么启动spring项目_springboot_08


启动listener操作:

spring项目启动如何应用banner 怎么启动spring项目_run_09


callRunner:

spring项目启动如何应用banner 怎么启动spring项目_springboot_10


根据不同的runner类进行不容的运行方式:

spring项目启动如何应用banner 怎么启动spring项目_run_11


listener.running:

执行监听的运行逻辑:进行事件注入

spring项目启动如何应用banner 怎么启动spring项目_springboot_12

总结

在进行spring-boot运行项目的时候,spring会判断我们的运行环境是否为web环境,这个在springApplication创建的时候就会指定,在run方法中也会进行处理。
run方法的流程:

  • 创建一个时间记录器 stopWatch
  • 进行配置Headless的属性
  • 获取监听器 getRunListeners(args)
  • 获取相关的参数
  • 对运行环境的准备
  • 配置忽略的bean
  • 执行banner
  • 创建applciationContext
  • 调整applicationContext
  • 刷新applicationContext
  • 启动runner
  • 注入相关event
  • 得到applicationContext