89961330 2019-11-16
SpringApplication类用于引导和启动一个Spring应用程序(即SpringBoot开发的应用)。通常用SpringBoot开发一个应用程序时,在主类的main函数中可以通过如下代码启动一个Spring应用:
@SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } }
SpringApplication的静态方法run(Class<?> primarySource, String... args))
的第一个参数接受一个Spring容器配置类(用Java代码对Spring容器进行配置)。第二个参数是命令行参数。将命令行参数转发给SpringApplication类,就可以在用java命令启动应用时,通过命令行参数对Spring应用做一些配置。
SpringApplication类会做如下事情启动应用:
下面我们通过SpringApplication的源码详细描述上述这些过程。
下面是SpringApplication类静态run方法的源码。可以看到,当我们调用这个静态run方法时,实际上会构造一个SpringApplication实例,然后再调用实例的run方法完成spring应用的启动。
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
下面是SpringApplication的构造函数,它主要完成下面初始化工作:
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(); }
下面对这些初始化过程进行一一说明:
SpringApplication能够从各种不同的配置源读取bean的定义。Spring Boot建议采用Java注解配置的方式提供一个全局唯一的配置类。但是,你可以同时使用多种不同的配置源。如果是Java注解的配置方式,会使用AnnotatedBeanDefinitionReader
加载配置(通过全类名)。如果是XML的配置方式,则会使用XmlBeanDefinitionReader
加载配置(通过XML文件地址)。
如果除了primarySources配置类以外,还需要其它的ApplicationContext配置源,则可以调用SpringApplication#setSources(Set<String> sources)
方法进行设置,该方法的参数既可以接受一个配置类的全类名,也可以是一个XML配置文件的地址。
SpringApplication默认的应用类型只有三种:
public enum WebApplicationType { /** * 非web类应用,无需内嵌web server */ NONE, /** * servlet类型的web应用,需要启动内嵌的web server */ SERVLET, /** * reactive类型的web应用,需要启动内嵌的reactive web server * 啥是reactive类型的web应用?目前还不知道^_^ */ REACTIVE;
判断的逻辑也非常简单,就是检查classpath下是否存在对应的类。
应用类型直接决定了要创建的ApplicationContext类型,下表整理了三种应用类型和所创建的ApplicationContext间的对应关系:
应用类型 | ApplicationContext类型 |
---|---|
NONE | AnnotationConfigApplicationContext |
SERVLET | AnnotationConfigServletWebServerApplicationContext |
REACTIVE | AnnotationConfigReactiveWebServerApplicationContext |
初始化ApplicationContextInitializer和ApplicationListener的过程比较相似,都是借助于SpringFactoriesLoader的方式完成初始化的,所以放到一起讲述。
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
SpringApplication的initializers就由这两部分配置的类共同组成,一共有6个ApplicationContextInitializer的实现类。
同理下面是关于listeners的配置,一共有10个ApplicationListener实现类。
# spring-boot-autoconfigure jar中的META-INF/spring.factories # Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.autoconfigure.BackgroundPreinitializer # spring-boot jar中的META-INF/spring.factories # Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.ClearCachesApplicationListener,\ org.springframework.boot.builder.ParentContextCloserApplicationListener,\ org.springframework.boot.context.FileEncodingApplicationListener,\ org.springframework.boot.context.config.AnsiOutputApplicationListener,\ org.springframework.boot.context.config.ConfigFileApplicationListener,\ org.springframework.boot.context.config.DelegatingApplicationListener,\ org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\ org.springframework.boot.context.logging.LoggingApplicationListener,\ org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
此处介绍一个IDEA使用的小技巧,我们可以借助IDEA的find usage功能启动找到哪里对ApplicationListener进行了配置,如下图所示:
推断主类过程的实现方式很巧妙,通过遍历异常堆栈找到方法名称是main的类,将其作为主类
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; }
在完成SpringApplication对象的构建和初始化之后,就开始执行run方法的逻辑了。具体代码如下,下面将介绍其执行流程。
public ConfigurableApplicationContext run(String... args) { 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); context = createApplicationContext(); 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); 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; }
/** - Load beans into the application context. - @param context the context to load beans into - @param sources the sources to load */ protected void load(ApplicationContext context, Object[] sources) { if (logger.isDebugEnabled()) { logger.debug( "Loading source " + StringUtils.arrayToCommaDelimitedString(sources)); } BeanDefinitionLoader loader = createBeanDefinitionLoader( getBeanDefinitionRegistry(context), sources); if (this.beanNameGenerator != null) { loader.setBeanNameGenerator(this.beanNameGenerator); } if (this.resourceLoader != null) { loader.setResourceLoader(this.resourceLoader); } if (this.environment != null) { loader.setEnvironment(this.environment); } loader.load(); }
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); } } }
至此,整个run方法执行结束,Spring应用也完成了整个启动流程。
SpringApplication类将一个SpringBoot应用的启动流程模板化,并在启动过程中提供了一些扩展点让我们可以根据具体需求进行扩展(通过SpringApplicationRunListener机制)。
SpringBoot提供的就是这样一个标准化的平台,在这个平台上既可以运行普通java应用,也可以运行web应用。SpringBoot平台会根据classpath自动判断应用类型,创建对应类型的ApplicationContext和加载恰当的配置。
当然,除了平台的能力外,SpringBoot还提供了很多XXXAutoConfiguration支持自动装配。我想,正是平台的能力和自动装配的能力,才让SpringBoot变得强大吧。