深入理解SpringApplication

用过 SpringBoot 的同学都知道,其程序的启动类是在一个main方法中调用SpringApplication.run方法执行的,如:

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

SpringApplication初始化

先看看SpringApplication的构造

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { 
	this.resourceLoader = resourceLoader;                                             
	Assert.notNull(primarySources, "PrimarySources must not be null");1this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));2this.webApplicationType = WebApplicationType.deduceFromClasspath();3setInitializers((Collection) getSpringFactoriesInstances(                         
			ApplicationContextInitializer.class));4setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));5this.mainApplicationClass = deduceMainApplicationClass();                         
}                                                                                     

主要的逻辑:

  1. 初始化Spring容器的配置类primarySources
  2. 推断应用程序的类型,进而根据应用程序的类型创建恰当的ApplicationContext
  3. 初始化指定的ApplicationContextInitializer列表
  4. 初始化指定的ApplicationListener列表
  5. 推断main class的类名称

决策WebApplicationType

具体第二步决策创建什么样的ApplicationContext

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

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

    private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
            + "web.servlet.DispatcherServlet";

    private WebApplicationType deduceWebApplicationType() {
        if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
                && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
            return WebApplicationType.REACTIVE;
        }
        for (String className : WEB_ENVIRONMENT_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        return WebApplicationType.SERVLET;
    }

根据 classpath 下是否存在某个特征类来决定是否应该创建一个为 Web 应用使用的ApplicationContext类型。具体判断为:

  1. 如果仅存在 Reactive 的包,则为WebApplicationType.REACTIVE类型;
  2. 如果 Servlet 和 Reactive的包都不存在,则为WebApplicationType.NONE类型;
  3. 其他情况都为WebApplicationType.SERVLET类型。

获取ApplicationContextInitializer和ApplicationListener

代码如下:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,         
		Class<?>[] parameterTypes, Object... args) {                         
	ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
	// 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;                                                        
}                                                                            

步骤:

  1. 通过SpringFactoriesLoader.loadFactoryNames(type, classLoader)方法,在 META-INF/spring.factories 文件下查找ApplicationContextInitializer类型对应的资源名称。
  2. 实例化上面的资源信息(初始化器)。
  3. 对初始化器根据Ordered接口或者@Order注解进行排序。

SpringFactoriesLoader会读取META-INF/spring.factories文件中的配置。一个工程项目中可以同时有多个META-INF/spring.factories文件(每个jar中都能有一个)。
例如在spring-boot-autoconfigure和spring-boot两个jar的META-INF/spring.factories文件中,均有针对ApplicationContextInitializer的配置:

# spring-boot-autoconfigure jar中的META-INF/spring.factories
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# spring-boot jar中的META-INF/spring.factories
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

拿到配置文件之后,就进行第二步的实例化

获取ApplicationListener的过程和这个是一样的,就不多哔哔了

推断主类

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

通过拿到堆栈信息,遍历方法名,如果是main,则是启动类(长见识)

SpringApplication启动(run)

把启动过程分成三部分来说,第一部分是打印banner及之前,第二部分为容器启动相关的代码,第三部分为容器启动之后的代码

public ConfigurableApplicationContext run(String... args) {1》
	StopWatch stopWatch = new StopWatch();                                          
	stopWatch.start();                                                              
	ConfigurableApplicationContext context = null;                                  
	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); 
	configureHeadlessProperty();                                                    
	SpringApplicationRunListeners listeners = getRunListeners(args);                
	listeners.starting();                                                           
	try {                                                                           
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(
				args);                                                              
		ConfigurableEnvironment environment = prepareEnvironment(listeners,         
				applicationArguments);                                              
		configureIgnoreBeanInfo(environment);                                       
		Banner printedBanner = printBanner(environment);2》
		context = createApplicationContext();                                       
		exceptionReporters = getSpringFactoriesInstances(                           
				SpringBootExceptionReporter.class,                                  
				new Class[] { ConfigurableApplicationContext.class }, context);     
		prepareContext(context, environment, listeners, applicationArguments,       
				printedBanner);                                                     
		refreshContext(context);                                                    
		afterRefresh(context, applicationArguments);                                
		stopWatch.stop();3if (this.logStartupInfo) {                                                  
			new StartupInfoLogger(this.mainApplicationClass)                        
					.logStarted(getApplicationLog(), stopWatch);                    
		}                                                                           
		listeners.started(context);                                                 
		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;                                                                 
}                                                                                   

应用启动准备阶段

  1. 方法执行伊始,会先创建一个StopWatch对象,该对象用于统计应用的启动时间。下图中的启动时间日志就是根据StopWatch对象统计的数据打印的
  2. 拿到SpringApplication初始化阶段生成的SpringApplicationRunListener
  3. 通知监听事件,应用开始启动了listeners.starting()
prepareEnvironment

加载属性配置。执行完成后,所有的environment的属性都会加载进来,包括 application.properties 和外部的属性配置。

private ConfigurableEnvironment prepareEnvironment(                                   
		SpringApplicationRunListeners listeners,                                      
		ApplicationArguments applicationArguments) {                                  
	// Create and configure the environment                                           
	ConfigurableEnvironment environment = getOrCreateEnvironment();                   
	configureEnvironment(environment, applicationArguments.getSourceArgs());          
	listeners.environmentPrepared(environment);                                       
	bindToSpringApplication(environment);                                             
	if (!this.isCustomEnvironment) {                                                  
		environment = new EnvironmentConverter(getClassLoader())                      
				.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
	}                                                                                 
	ConfigurationPropertySources.attach(environment);                                 
	return environment;                                                               
}                                                                                     

configureEnvironment

protected void configureEnvironment(ConfigurableEnvironment environment,
		String[] args) {                                                
	configurePropertySources(environment, args);                        
	configureProfiles(environment, args);                               
}                                                                       

应用启动

第二部分的逻辑

createApplicationContext

根据webApplicationType创建响应的ApplicationContext

protected ConfigurableApplicationContext createApplicationContext() {                
	Class<?> contextClass = this.applicationContextClass;                            
	if (contextClass == null) {                                                      
		try {                                                                        
			switch (this.webApplicationType) {                                       
			case SERVLET:                                                            
				contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);     
				break;                                                               
			case REACTIVE:                                                           
				contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);    
				break;                                                               
			default:                                                                 
				contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);                 
			}                                                                        
		}                                                                            
		catch (ClassNotFoundException ex) {                                          
			throw new IllegalStateException(                                         
					"Unable create a default ApplicationContext, "                   
							+ "please specify an ApplicationContextClass",           
					ex);                                                             
		}                                                                            
	}                                                                                
	return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}                                                                                    

我们以 SERVLET 类型为例,它会创建AnnotationConfigServletWebServerApplicationContext应用上下文实例。

获取 Spring 异常报告器

getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context)方法,获取 META-INF/spring.factories 文件下类型为SpringBootExceptionReporter的资源实例:

# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers
prepareContext

Spring 应用上下文启动前的准备工作:

private void prepareContext(ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        //设置 context 的 environment 属性
        context.setEnvironment(environment);
        // Spring 应用上下文的后置处理
        postProcessApplicationContext(context);
        // 运用 Spring 应用上下文初始化器
        applyInitializers(context);
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }

        // Add boot specific singleton beans
        context.getBeanFactory().registerSingleton("springApplicationArguments",
                applicationArguments);
        if (printedBanner != null) {
            context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
        }

        // Load the sources
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        // 加载 BeanDefinition
        load(context, sources.toArray(new Object[0]));
        listeners.contextLoaded(context);
    }

大致流程为:

  1. context的属性做赋值,如设置环境变量,调用初始化器来初始化context
  2. 获取所有配置源信息,包括 Configuration Class、类名、包名及Spring XML 配置资源路径信息。
  3. 加载 Spring 应用上下文配置源。将BeanDefinition加载到context中。
  4. 发布上下文已准备事件ApplicationPreparedEvent

Spring Boot 中的BeanDefinitionLoaderBeanDefinition读取的综合实现。当其load()方法调用时,各个BeanDefinitionReader类型的属性各司其职,为 Spring 应用上下文从不同的配置源装载 Spring Bean 定义(BeanDefinition)。

ApplicationContext 启动阶段

这个后续专门写一篇吧,这里知道spring容器启动了就行

ApplicationContext 启动后阶段

这是一个空方法

应用启动完成

最主要有个callRunners方法,来调用实现了CommandLineRunner或者ApplicationRunner接口的类的 run 方法,得以满足需要在 Spring 应用上下文完全准备完毕后,执行一些操作的场景。

private void callRunners(ApplicationContext context, ApplicationArguments args) {
	List<Object> runners = new ArrayList<>();                                    
	runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());    
	runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());    
	AnnotationAwareOrderComparator.sort(runners);                                
	for (Object runner : new LinkedHashSet<>(runners)) {                         
		if (runner instanceof ApplicationRunner) {                               
			callRunner((ApplicationRunner) runner, args);                        
		}                                                                        
		if (runner instanceof CommandLineRunner) {                               
			callRunner((CommandLineRunner) runner, args);                        
		}                                                                        
	}                                                                            
}                                                                                

总结

SpringApplication类将一个SpringBoot应用的启动流程模板化,并在启动过程中提供了一些扩展点让我们可以根据具体需求进行扩展(通过SpringApplicationRunListener机制)。

来源

https://segmentfault.com/a/1190000019560001

https://www.jianshu.com/p/c2789f1548ab

http://svip.iocoder.cn/Spring-Boot/SpringApplication/

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值