Spring Boot 启动流程源码分析

学习过springboot的都知道,在Springboot的main入口函数中调用SpringApplication.run(DemoApplication.class,args)函数便可以启用SpringBoot应用程序,跟踪一下SpringApplication源码可以发现,最终还是调用了SpringApplication的动态run函数。

下面以SpringBoot2.0.3.RELEASE为例简单分析一下运行过程。

SpringApplicatiton部分源码:

1 public static ConfigurableApplicationContext run(Class<?>[] primarySources,
2       String[] args) {
3   //创建springapplication对象,调用函数run(args)
4    return new SpringApplication(primarySources).run(args);
5 }

上面的源码可以发现还是先创建SpringApplication实例,再调用run方法

第一步 分析 SpringApplication构造函数

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

  //1:判断web环境
  this.webApplicationType = deduceWebApplicationType();

  //2:加载classpath下META-INF/spring.factories中配置的ApplicationContextInitializer
  setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
  //3:加载classpath下META-INF/spring.factories中配置的ApplicationListener

  setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  //4:推断main方法所在的类
  this.mainApplicationClass = deduceMainApplicationClass();
}

具体逻辑分析:

1.deduceWebApplicationType(),  SpringApplication构造函数中首先初始化应用类型,根据加载相关类路径判断应用类型,具体逻辑如下:

   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 static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" };

   private WebApplicationType deduceWebApplicationType() {
     //当类路径中存在REACTIVE_WEB_ENVIRONMENT_CLASS并且不存在MVC_WEB_ENVIRONMENT_CLASS时
     if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
     return WebApplicationType.REACTIVE;
   }
   //当加载的类路径中不包含WEB_ENVIRONMENT_CLASSES中定义的任何一个类时,返回标准应用()
   for (String className : WEB_ENVIRONMENT_CLASSES) {
     if (!ClassUtils.isPresent(className, null)) {

      return WebApplicationType.NONE;
     }
   }
     //加载的类路径中包含了WEB_ENVIRONMENT_CLASSES中定义的所有类型则判断为servlet的web应用
   return WebApplicationType.SERVLET;
 }

2.setInitializers初始化属性initializers,加载classpath下META-INF/spring.factories中配置的ApplicationContextInitializer,此处getSpringFactoriesInstances方法入参type=ApplicationContextInitializer.class

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
  // SpringFactoriesLoader.loadFactoryNames()方法将会从calssptah下的META-INF/spring.factories中读取key为//org.springframework.context.ApplicationContextInitializer的值,并以集合形式返回
  Set<String> names = new LinkedHashSet<>(
  SpringFactoriesLoader.loadFactoryNames(type, classLoader));
  //根据返回names集合逐个实例化,也就是初始化各种ApplicationContextInitializer,这些Initializer实际是在Spring上下文ApplicationContext执行refresh前调用
   List<T> instances = createSpringFactoriesInstances(type, parameterTypes,classLoader, args, names);
  AnnotationAwareOrderComparator.sort(instances); //对instance排序
  return instances;
}

以我的demo为例,实际debug时得到的initializers如下,其中数据来源于spring-boot,spring-boot-autoconfiguration和spring-boot-devtolls三个jar包下的classpath中,ApplicationContextInitializer接口是Spring框架提供地的,其主要作用是在Spring容器初始化过程中prepareContext()这一步进行回调

 

 3. setListeners 初始化属性listeners,加载classpath下META-INF/spring.factories中配置的ApplicationListener,此处入参为getSpringFactoriesInstances方法入参type= ApplicationListener.class

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
  // SpringFactoriesLoader.loadFactoryNames()方法将会从calssptah下的META-INF/spring.factories中读取key为//org.springframework.context.ApplicationListener的值,并以集合形式返回
  Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
  //根据配置,初始化各种ApplicationListener,作用是用来监听ApplicationEvent
  List<T> instances = createSpringFactoriesInstances(type, parameterTypes,classLoader, args, names);
  AnnotationAwareOrderComparator.sort(instances);
  return instances;
}

 

第二步 分析 SpringApplication中 run方法

SpringApplication的run方法代码如下:

public ConfigurableApplicationContext run(String... args) {
  StopWatch stopWatch = new StopWatch();
  stopWatch.start();
  ConfigurableApplicationContext context = null;
  Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
  //设置系统变量java.awt.headless
  configureHeadlessProperty();
  //1:获取监听器:加载classpath下面的META-INF/spring.factories配置的监听器SpringApplicationRunListener
  SpringApplicationRunListeners listeners = getRunListeners(args);
  //2:启动监听器:执行所有runlistener的starting方法,实际上发布一个【ApplicationStartingEvent】事件
  listeners.starting();
  try {
  //3:实例化ApplicationArguments对象
  ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
  //4: 准备应用上下文环境Environment (web环境 or 标准环境)+配置Environment,主要是把run方法的参数配置到Environment 发布【ApplicationEnvironmentPreparedEvent】事件
  ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
  configureIgnoreBeanInfo(environment);
  //打印banner,SpringBoot启动时,控制台输出的一个歪歪扭扭的很不清楚的Spring几个大字母,也可以自定义
  Banner printedBanner = printBanner(environment);
  //5: 根据不同environment实例化上下文 context
  context = createApplicationContext();
  // 异常处理,实例化一个SpringBootExceptionReporter.class 用于处理启动过程中的错误
  exceptionReporters = getSpringFactoriesInstances(
  SpringBootExceptionReporter.class,
  new Class[] { ConfigurableApplicationContext.class }, context);
  //6: 上下文相关预处理 发布【ApplicationPreparedEvent】事件
  prepareContext(context, environment, listeners, applicationArguments,
  printedBanner);
  //7: 【刷新应用上线文】执行spring容器(context)的refresh方法,并且调用context的registerShutdownHook方法
  refreshContext(context);
  //8:空方法,用于扩展
  afterRefresh(context, applicationArguments);
  stopWatch.stop();
  if (this.logStartupInfo) {
    new StartupInfoLogger(this.mainApplicationClass)
    .logStarted(getApplicationLog(), stopWatch);
  }
    //9:执行所有runlisteners的started方法,发布【ApplicationStartedEvent】事件
  listeners.started(context);
    //10: 遍历执行CommandLineRunner和ApplicationRunner
    //如果需要在SpringBoot应用启动后运行一些特殊的逻辑,可以通过实现ApplicationRunner或CommandLineRunner接口中的run方法,该自定义类的run方法会在此处统一调用
  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. 获取监听器:getRunListeners(args) 加载各种SpringApplicationRunListener实例,内部实现也还是通过SpringFactoriesLoader.loadFactoryNames(type, classLoader))实现,加载META-INF/spring.factories中key为org.springframework.boot.SpringApplicationRunListener的值,生成对应实例,SpringBoot实际加载了一个EventPublishingRunListener监听器,该监听器继承SpringApplicationRunListener接口,SpringApplicationRunListener规定了SpringBoot的生命周期,在各个生命周期广播相应的事件,调用实际的ApplicationListener类。

  2. 启动监听器: listeners.starting()  执行所有SpringApplicationRunListener的stating方法,发布ApplicationStartedEvent事件,该事件被ApplicationListener类型的listener监听

  3.  实例化ApplicationArguments对象

  4 . 准备应用上下文环境 并发布ApplicationEnvironmentPreparedEvent事件

private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
   // Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
  //根据properties和profiles配置环境
configureEnvironment(environment, applicationArguments.getSourceArgs());
  // 执行EventPublishingRunListener发布ApplicationEnvironmentPreparedEvent事件,将会被ApplicationListener监听到
listeners.environmentPrepared(environment);
   //
bindToSpringApplication(environment);
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}

备注:实际上载spring-boot-2.0.3.RELEASE.jar包中,可以发现spring.factories中只配置了一个RunListener: org.springframework.boot.context.event.EventPublishingRunListener截取EventPublishingRunListener.java部分代码:

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {


public EventPublishingRunListener(SpringApplication application, String[] args) {
     this.application = application;
     this.args = args;
    this.initialMulticaster = new SimpleApplicationEventMulticaster();
      //将SpringApplication实例中的ApplicationListener类型的listeners添加到initialMulticaster,后续执行监听
  for (ApplicationListener<?> listener : application.getListeners()) {
   this.initialMulticaster.addApplicationListener(listener);
   }
}

  // 发布一个ApplicationEnvironmentPreparedEvent事件
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
//所有被添加到initialMulticaster中的listener都将监听ApplicationEnvironmentPreparedEvent事件
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
this.application, this.args, environment));
}

}

4.1 根据properties和profiles配置环境:configureEnvironment(environment, applicationArguments.getSourceArgs());

       以下假设指定配文件application-dev.properties,跟踪一下源码,可以发现:

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

configureEnvironment方法内部比较简洁,直接调用两个方法完事,

        configurePropertySources(environment, args)方法的作用是将args封装成了SimpleCommandLinePropertySource并加入到了environment中,其中arg中含有启动参数:--spring.profiles.active=dev

        configureProfiles(environment, args)作用是将启动参数中指定的配置文件激活。

 

configureProfiles中执行enviroment.getActiveProfiles():强制读取启动命令中指定的配置文件

protected Set<String> doGetActiveProfiles() {
synchronized (this.activeProfiles) {
if (this.activeProfiles.isEmpty()) {
String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
if (StringUtils.hasText(profiles)) {
setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(profiles)));
}
}
return this.activeProfiles;
}
}

  5. 根据environment类型创建ApplicationContext,通常情况下,我们启动的是一个Servlet应用,debug进createApplicationContext()源码,可以看到内部初始化了AnnotationConfigServletWebServerApplicationContext类,也就是我们的上下文context

  6. 上下文相关预处理,prepareContext()方法

private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment); //设置容器环境,environment前面已经准备好
  //配置beanNameGenerator和资源加载器
postProcessApplicationContext(context);
  //回调所有的ApplicationContextInitializer初始化器
applyInitializers(context);
  //发布容器以准备好的事件:执行所有SpringApplicationRunListener的contextPrepared方法,触发事件,实际上EventPublishingRunListener中contextPrepared是一个空方法,什么都没执行
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}

//向Spring容器注入springApplicationArguments和springBootBanner,实际执行是将main函数的参数args和printedBanner分别封装成单例bean注册到容器中。
// 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"); //加载启动类,将启动类也注入容器
load(context, sources.toArray(new Object[0]));
//发布容器已加载事件:执行所有SpringApplicationRunListener的contextLoaded方法,下面是EventPublishingRunListener中的contextLoaded
listeners.contextLoaded(context);
}

 具体load方法实现, load方法的作用其注释写的也很清楚,"Load beans into the application context",实际执行时可以发现主要是将我们的SpringBoot应用的启动类(此处SpringbootdemoApplication2)注册到容器中!前面if条件都为false,不执行,具体分析loader.loader()。

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

loader.load()源码:

public int load() {
int count = 0;
for (Object source : this.sources) {
count += load(source);
}
return count;
}

private int load(Object source) {
Assert.notNull(source, "Source must not be null");
if (source instanceof Class<?>) {
return load((Class<?>) source); //执行
}
if (source instanceof Resource) {
return load((Resource) source);
}
if (source instanceof Package) {
return load((Package) source);
}
if (source instanceof CharSequence) {
return load((CharSequence) source);
}
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}

private int load(Class<?> source) {
if (isGroovyPresent()
&& GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
// Any GroovyLoaders added in beans{} DSL can contribute beans here
GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source,
GroovyBeanDefinitionSource.class);
load(loader);
}
if (isComponent(source)) {
this.annotatedReader.register(source);
return 1;
}
return 0;
}

 isComponent(source)方法判断当前类是不是有@Component注解,显然启动类的@SpringBootApplication这个组合注解是包含Component注解的。

   this.annotatedReader.register(source)方法内部调用Spring框架底层提供的AnnotatedBeanDefinitionReader.doRegisterBean(xxx)方法,最终将SpringBoot应用的启动类注册到容器中。

 

 

 

 7.  执行context的refresh,并且调用context的registerShutdownHook方法,refresh方法的具体逻辑分析可以参考:

      Spring源码解析 – AnnotationConfigApplicationContext容器创建过程

8. afterRefresh空方法

9. 执行所有runlisteners的started方法,发布ApplicationStartedEvent事件

10. 遍历执行CommandLineRunner和ApplicationRunner以上。

 
 
posted @ 2022-01-20 09:52  bluesky1  阅读(261)  评论(0编辑  收藏  举报