窥探-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);
}
绑定environment到springApplication:
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);
加载资源:
refreshContext方法
刷新context:
applicationCpntext.refresh方法
启动listener操作:
callRunner:
根据不同的runner类进行不容的运行方式:
listener.running:
执行监听的运行逻辑:进行事件注入
总结
在进行spring-boot运行项目的时候,spring会判断我们的运行环境是否为web环境,这个在springApplication创建的时候就会指定,在run方法中也会进行处理。
run方法的流程:
- 创建一个时间记录器 stopWatch
- 进行配置Headless的属性
- 获取监听器 getRunListeners(args)
- 获取相关的参数
- 对运行环境的准备
- 配置忽略的bean
- 执行banner
- 创建applciationContext
- 调整applicationContext
- 刷新applicationContext
- 启动runner
- 注入相关event
- 得到applicationContext