Spring Boot启动过程完全解析(一)

之前在排查一个线上问题时,不得不仔细跑了很多遍Spring Boot的代码,于是整理一下,我用的是1.4.3.RELEASE。

  首先,普通的入口,这没什么好说的,我就随便贴贴代码了:

SpringApplication.run(Application.class, args);
-->
  public static ConfigurableApplicationContext run(Object source, String... args) {
    return run(new Object[] { source }, args);
  }
  public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
    return new SpringApplication(sources).run(args);
  }

  也就是一个静态方法,调用了构造函数创建实例,构造的参数是Object数组,这里new这个数组的时候传入了一个元素就是启动类的类对象实例(一般就是“new Object[] { Application.class” }),构造函数里调用了一个initialize方法。

  SpringApplication的initialize方法,首先在Object数组有值的情况下将数组放入一个final的类实例私有Object的Set集合中;然后用deduceWebEnvironment方法判断当前应用环境是否是web环境,判断逻辑是看Classpath是否同时存在javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext,缺一就认为不是。然后,调用setInitializers方法,设置类实例的私有List<ApplicationContextInitializer<?>>类型变量initializers:

 public void setInitializers(
   Collection<? extends ApplicationContextInitializer<?>> initializers) {
  this.initializers = new ArrayList<ApplicationContextInitializer<?>>();
  this.initializers.addAll(initializers);
 }

  设置的时候会先new,也就是说这方法每次都是整体更换,不会追加。这个方法的参数都是各个模块中配置在META-INF/spring.factories中的key为org.springframework.context.ApplicationContextInitializer的值,这些类都是接口ApplicationContextInitializer<C extends ConfigurableApplicationContext>的泛型实现。

 private <T> Collection<? extends 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<String>(
    SpringFactoriesLoader.loadFactoryNames(type, classLoader));
  List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
    classLoader, args, names);
  AnnotationAwareOrderComparator.sort(instances);
  return instances;
 }

  使用SpringFactoriesLoader.loadFactoryNames方法去取上面说的被配置的ApplicationContextInitializer的名字放进Set<String>中,并用反射创建这些名字的实例。

  setInitializers方法之后又是setInitializers,参数同上都是getSpringFactoriesInstances方法获取,只不过这次参数Class<T> type泛型类型是org.springframework.context.ApplicationListener。

  initialize方法的最后一个步是设置实例的Class<?>类型私有属性mainApplicationClass,获取设置值的方法deduceMainApplicationClass:

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实例方法(注意不是上面的静态方法)。一进run方法首先启动了StopWatch,这个StopWatch的功能在类的注释写可,大概意思是这是个简单的秒表,用于在开发过程中方便程序员调试性能等,非线程安全,不建议用于生产。configureHeadlessProperty设置使用Headless,对于只有远程登录使用的服务器来说这样性能要好一些。接着是加载用于这个run方法启动过程的监听器,依然是getSpringFactoriesInstances方法,这次的类型是org.springframework.boot.SpringApplicationRunListener:

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
  

 SpringApplicationRunListeners(Log log,
   Collection<? extends SpringApplicationRunListener> listeners) {
  this.log = log;
  this.listeners = new ArrayList<SpringApplicationRunListener>(listeners);
 }

  先是加载所有可用监听,然后初始化SpringApplicationRunListeners对象,最后循环启动所有SpringApplicationRunListener监听。启动监听的方法:

 @Override
 public void started() {
  this.initialMulticaster
    .multicastEvent(new ApplicationStartedEvent(this.application, this.args));
 }

  ApplicationStartedEvent实例化传了两个参数,先看第一个参数this.application是怎么来的,实例的SpringApplication的run方法中,用于获取SpringApplicationRunListener,也就是前面说的getSpringFactoriesInstances被调用时:

 private SpringApplicationRunListeners getRunListeners(String[] args) {
  Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
  return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
    SpringApplicationRunListener.class, types, this, args));
 }

  getSpringFactoriesInstances方法的参数包含SpringApplication.class和this,这两个参数被传入createSpringFactoriesInstances方法:

  可以看到,是通过反射创建实例的时候,将SpringApplication中的this传进来EventPublishingRunListener构造的,然后EventPublishingRunListener构造:

public EventPublishingRunListener(SpringApplication application, String[] args) {
  this.application = application;
  this.args = args;
  this.initialMulticaster = new SimpleApplicationEventMulticaster();
  for (ApplicationListener<?> listener : application.getListeners()) {
   this.initialMulticaster.addApplicationListener(listener);
  }
 }

  最后在构造ApplicationStartedEvent时传给它的基类EventObject的protected不可序列化属性source。实例化ApplicationStartedEvent后instance.getClass()并包装为ResolvableType类型以保存类型信息,并将它和event作为参数传入SimpleApplicationEventMulticaster的multicastEvent方法。multicastEvent首先获取ApplicationListener,使用getApplicationListeners方法,这个方法中抛开对listener做了一些缓存类工作外,主要就是将事件和对应的监听器做了下是否支持的验证,返回通过了retrieveApplicationListeners中通过了supportsEvent验证的监听器集合,这里就体现出了ResolvableType的作用,它保存了类型的信息同时对泛型类型也支持。

  得到了这些匹配的监听器后,判断当前Executor是否被设置过,如果为null则同步循环执行所有:invokeListener(listener, event);如果不为null则:

executor.execute(new Runnable() {
     @Override
     public void run() {
      invokeListener(listener, event);
     }
    });

  监听器执行的时候也会先判断是否是该由自己处理的事件,例如:

 @Override
 public void onApplicationEvent(ApplicationEvent event) {
  if (event instanceof ApplicationEnvironmentPreparedEvent) {
   onApplicationEnvironmentPreparedEvent(
     (ApplicationEnvironmentPreparedEvent) event);
  }
  if (event instanceof ApplicationPreparedEvent) {
   onApplicationPreparedEvent(event);
  }
 }

  监听启动后,只准备一些启动参数,和环境变量prepareEnvironment方法先是读取了应用的启动参数和profile配置,然后用listeners.environmentPrepared(environment)传给监听器:

 public void environmentPrepared(ConfigurableEnvironment environment) {
  this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
    this.application, this.args, environment));
 }

  接着判断如果environment是org.springframework.web.context.ConfigurableWebEnvironment的实例,但webEnvironment不是true,也就是说存在org.springframework.web.context.ConfigurableWebEnvironmen但不存在javax.servlet.Servlet的情况,会多执行一步environment = convertToStandardEnvironment(environment)转换。

  之后的printBanner就不细说了,如果你在resource下自定义了一个banner.txt文件,启动时会输出内容,否则输出:

.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.4.3.RELEASE)

  接着创建ConfigurableApplicationContext实例,方法也很简单,如果是web环境就BeanUtils.instantiate一个org.springframework.boot.context.embedded. AnnotationConfigEmbeddedWebApplicationContext的实例并强转为ConfigurableApplicationContext,否则用org.springframework.context.annotation. AnnotationConfigApplicationContext的实例强转。

  创建FailureAnalyzers实例,记录了ConfigurableApplicationContext实例中需要关注的部分,如果启动出错了可以据此分析,可以配置,具体的逻辑依然是老方法spring.factories:

  不同的Analyzer关注不同的部分,自己可以扩展配置,最后prepareFailureAnalyzers方法给所有Analyzer实例setBeanFactory(context.getBeanFactory()),一旦启动过程进入catch,被注册的Analyzer实例的analyze方法就会被触发执行,分析结果会被loggedExceptions.add(exception)加入到抛出的异常中:

private FailureAnalysis analyze(Throwable failure, List<FailureAnalyzer> analyzers) {
  for (FailureAnalyzer analyzer : analyzers) {
   FailureAnalysis analysis = analyzer.analyze(failure);
   if (analysis != null) {
    return analysis;
   }
  }
  return null;
 }

例如:NoSuchBeanDefinitionFailureAnalyzer

 @Override
 protected FailureAnalysis analyze(Throwable rootFailure,
   NoSuchBeanDefinitionException cause, String description) {
  if (cause.getNumberOfBeansFound() != 0) {
   return null;
  }
  List<AutoConfigurationResult> autoConfigurationResults = getAutoConfigurationResults(
    cause);
  StringBuilder message = new StringBuilder();
  message.append(String.format("%s required %s that could not be found.%n",
    description == null ? "A component" : description,
    getBeanDescription(cause)));
  if (!autoConfigurationResults.isEmpty()) {
   for (AutoConfigurationResult provider : autoConfigurationResults) {
    message.append(String.format("\t- %s%n", provider));
   }
  }
  String action = String.format("Consider %s %s in your configuration.",
    (!autoConfigurationResults.isEmpty()
      ? "revisiting the conditions above or defining" : "defining"),
    getBeanDescription(cause));
  return new FailureAnalysis(message.toString(), action, cause);
 }

  prepareContext方法中postProcessApplicationContext会在this.beanNameGenerator存在的情况下加载自定义命名策略,然后在this.resourceLoader存在的情况下为context设置resourceLoader和classLoader。applyInitializers方法调用之前加载的Initializer的实例并执行其initialize方法,例如加载环境变量信息、注册EmbeddedServletContainerInitializedEvent的监听、注册CachingMetadataReaderFactoryPostProcessor等。listeners.contextPrepared(context)由于EventPublishingRunListener的contextPrepared是空的,先不说了。logStartupInfo部分初始化了logger,然后根据配置情况打印了启动或运行以及profile是否配置的日志:

protected void logStartupInfo(boolean isRoot) {
  if (isRoot) {
   new StartupInfoLogger(this.mainApplicationClass)
     .logStarting(getApplicationLog());
  }
 }
 protected Log getApplicationLog() {
  if (this.mainApplicationClass == null) {
   return logger;
  }
  return LogFactory.getLog(this.mainApplicationClass);
 }
 public void logStarting(Log log) {
  Assert.notNull(log, "Log must not be null");
  if (log.isInfoEnabled()) {
   log.info(getStartupMessage());
  }
  if (log.isDebugEnabled()) {
   log.debug(getRunningMessage());
  }
 }
 protected void logStartupProfileInfo(ConfigurableApplicationContext context) {
  Log log = getApplicationLog();
  if (log.isInfoEnabled()) {
   String[] activeProfiles = context.getEnvironment().getActiveProfiles();
   if (ObjectUtils.isEmpty(activeProfiles)) {
    String[] defaultProfiles = context.getEnvironment().getDefaultProfiles();
    log.info("No active profile set, falling back to default profiles: "
      + StringUtils.arrayToCommaDelimitedString(defaultProfiles));
   }
   else {
    log.info("The following profiles are active: "
      + StringUtils.arrayToCommaDelimitedString(activeProfiles));
   }
  }
 }

  接着prepareContext中注册启动参数(applicationArguments)到bean工厂,包括logger、commandLineArgs等。然后加载bean定义的来源并根据其中配置加载bean,这里的sources就是初始化启动类时传进来的那个sources:

 BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
    Assert.notNull(registry, "Registry must not be null");
    Assert.notEmpty(sources, "Sources must not be empty");
    this.sources = sources;
    this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
    this.xmlReader = new XmlBeanDefinitionReader(registry);
    if (isGroovyPresent()) {
      this.groovyReader = new GroovyBeanDefinitionReader(registry);
    }
    this.scanner = new ClassPathBeanDefinitionScanner(registry);
    this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
  }

  注意下面的sources是待加载的,和上面这段不是同一个:

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

  类型不同加载过程不同,其中Class<?>加载过程大概是通过BeanDefinitionLoader调用AnnotatedBeanDefinitionReader的registerBean方法:

public void registerBean(Class<?> annotatedClass, String name, Class<? extends Annotation>... qualifiers) {
    AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
    if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
      return;
    }
    ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
    abd.setScope(scopeMetadata.getScopeName());
    String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
    AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
    if (qualifiers != null) {
      for (Class<? extends Annotation> qualifier : qualifiers) {
        if (Primary.class == qualifier) {
          abd.setPrimary(true);
        }
        else if (Lazy.class == qualifier) {
          abd.setLazyInit(true);
        }
        else {
          abd.addQualifier(new AutowireCandidateQualifier(qualifier));
        }
      }
    }
    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
    definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
    BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
  }

  可以看到有生成方法名,设置默认注入的实例、延迟以及过滤等等,注入的过程包括初始化一些信息,如构造、内部类、注解等:

protected AbstractBeanDefinition(ConstructorArgumentValues cargs, MutablePropertyValues pvs) {
    setConstructorArgumentValues(cargs);
    setPropertyValues(pvs);
  }
  public StandardAnnotationMetadata(Class<?> introspectedClass, boolean nestedAnnotationsAsMap) {
    super(introspectedClass);
    this.annotations = introspectedClass.getAnnotations();
    this.nestedAnnotationsAsMap = nestedAnnotationsAsMap;
  }

  其他三种比如有的有输入流什么的就不细总结了,这部分介绍Spring IOC的相关文章应该不少。

  prepareContext方法最后listeners.contextLoaded(context),加载监听器到context并广播ApplicationPreparedEvent事件。

咱最近用的github:https://github.com/saaavsaaa

以上所述是小编给大家介绍的Spring Boot启动过程完全解析(一),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • spring boot启动加载数据原理分析

    实际应用中,我们会有在项目服务启动的时候就去加载一些数据或做一些事情这样的需求. 为了解决这样的问题,spring Boot 为我们提供了一个方法,通过实现接口 CommandLineRunner 来实现. 创建实现接口 CommandLineRunner 的类,通过@Component注解,就可以实现启动时加载数据项.使用@Order 注解来定义执行顺序. IndexStartupRunner.Java类: import org.springframework.boot.CommandLine

  • Spring Boot启动过程完全解析(二)

    上篇给大家介绍了Spring Boot启动过程完全解析(一),大家可以点击参考下 该说refreshContext(context)了,首先是判断context是否是AbstractApplicationContext派生类的实例,之后调用了强转为AbstractApplicationContext类型并调用它的refresh方法.由于AnnotationConfigEmbeddedWebApplicationContext继承自EmbeddedWebApplicationContext,所以会

  • Spring Boot启动流程分析

    引言 早在15年的时候就开始用spring boot进行开发了,然而一直就只是用用,并没有深入去了解spring boot是以什么原理怎样工作的,说来也惭愧.今天让我们从spring boot启动开始,深入了解一下spring boot的工作原理. 为什么用spring boot 在使用一个东西或者一个工具之前,我们总是会问自己,我为什么要用?用他能给我带来什么好处? * 最大的好处就是spring boot遵从了java**约定大于配置**不用面对一大堆的配置文件,spring boot是根据

  • SpringBoot Tomcat启动实例代码详解

    废话不多了,具体内容如下所示: Application configuration class: @SpringBootApplication public class ServletInitializer extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return appli

  • Spring Boot启动过程全面解析(三)

    我已经很精简了,两篇(Spring Boot启动过程(一).spring Boot启动过程(二))依然没写完,接着来. refreshContext之后的方法是afterRefresh,这名字起的真...好.afterRefresh方法内只调用了callRunners一个方法,这个方法从上下文中获取了所有的ApplicationRunner和CommandLineRunner接口的实现类,并执行这些实现类的run方法.例如Spring Batch的JobLauncherCommandLineRu

  • spring boot在启动项目之后执行的实现方法

    前言 我们在web项目启动之后有时候还会做点其它的东西(比如,导入数据脚本),下面就说说spring-boot里怎么在程序启动后加入自己要执行的东西 方法如下: 新建一个类:BeforeStartup.java @Configuration public class BeforeStartup implements ApplicationListener<ContextRefreshedEvent> { @Autowired private InitDB initDB; @Override p

  • Spring Boot启动过程完全解析(一)

    之前在排查一个线上问题时,不得不仔细跑了很多遍Spring Boot的代码,于是整理一下,我用的是1.4.3.RELEASE. 首先,普通的入口,这没什么好说的,我就随便贴贴代码了: SpringApplication.run(Application.class, args); --> public static ConfigurableApplicationContext run(Object source, String... args) { return run(new Object[]

  • Spring Boot启动过程(四)之Spring Boot内嵌Tomcat启动

    之前在Spring Boot启动过程(二)提到过createEmbeddedServletContainer创建了内嵌的Servlet容器,我用的是默认的Tomcat. private void createEmbeddedServletContainer() { EmbeddedServletContainer localContainer = this.embeddedServletContainer; ServletContext localServletContext = getServ

  • Spring Boot启动过程(六)之内嵌Tomcat中StandardHost、StandardContext和StandardWrapper的启动教程详解

    StandardEngine[Tomcat].StandardHost[localhost]的启动与StandardEngine不在同一个线程中,它的start: // Start our child containers, if any Container children[] = findChildren(); List<Future<Void>> results = new ArrayList<>(); for (int i = 0; i < childre

  • Spring Boot启动过程(五)之Springboot内嵌Tomcat对象的start教程详解

    标题和Spring Boot启动过程(四)之Spring Boot内嵌Tomcat启动很像,所以特别强调一下,这个是Tomcat对象的. 从TomcatEmbeddedServletContainer的this.tomcat.start()开始,主要是利用LifecycleBase对这一套容器(engine,host,context及wrapper)进行启动并发布诸如configure_start.before_init.after_start的lifecycleEvent事件给相应的监听器(如

  • Spring Boot启动流程断点过程解析

    这篇文章主要介绍了Spring Boot启动流程断点过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 启动入口 跟进run方法 : 一个用来使用默认的配置从特定的源运行SpringApplication的静态帮助类. 这个类有两个重载方法,另一个用来传入多个源.通常,单个参数方法是数组方法的一个特例 创建一个新的SpringApplication实例.这个应用程序上下文会从特定的源加载Beans,这个实例会在调用run方法之前被定制化.

  • 如何在Spring Boot启动时运行定制的代码

    Spring Boot会自动为我们做很多配置,但迟早你需要做一些自定义工作.在本文中,您将学习如何挂钩应用程序引导程序生命周期并在Spring Boot启动时执行代码. 1.执行bean初始化的方法 Spring启动应用程序后运行某些逻辑的最简单方法是将代码作为所选bean引导过程的一部分来执行. 只需创建一个类,将其标记为Spring组件,并将应用程序初始化代码放在带有@PostConstruct注释的方法中.理论上,您可以使用构造函数而不是单独的方法,但将对象的构造与其实际责任分开是一种很好

  • Spring Boot启动时调用自己的非web逻辑

    在spring Boot中,有些代码是WEB功能,例如API等,但是有些逻辑是非WEB,启动时就要调用并持续运行的,该如何加载自己的非WEB逻辑呢? SpringBootApplication类实现CommandLineRunner并覆盖run()方法 @SpringBootApplication public class ZjkApplication implements CommandLineRunner{ /** * @param args */ public static void ma

随机推荐