欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring Boot启动流程分析

程序员文章站 2024-02-29 12:44:28
引言 早在15年的时候就开始用spring boot进行开发了,然而一直就只是用用,并没有深入去了解spring boot是以什么原理怎样工作的,说来也惭愧。今天让我们从...

引言

早在15年的时候就开始用spring boot进行开发了,然而一直就只是用用,并没有深入去了解spring boot是以什么原理怎样工作的,说来也惭愧。今天让我们从spring boot启动开始,深入了解一下spring boot的工作原理。

为什么用spring boot

在使用一个东西或者一个工具之前,我们总是会问自己,我为什么要用?用他能给我带来什么好处?

* 最大的好处就是spring boot遵从了java**约定大于配置**不用面对一大堆的配置文件,spring boot是根据你用的包来决定提供什么配置。

* 服务器以jar包的形式内嵌于项目中,对于微服务满天飞的情况,spring boot天生适合微服务架构,方便部署。

* 提供devtools从此改代码就需重启成为历史。

有优点就一定有缺点,缺点来源于优点优点来源于缺点(感觉在说哲学问题了哈哈哈)

* 正因为配置对开发者不透明,不看源码会不清楚spring boot如何进行诸如jdbc加载、事务管理等,出现错误也很难调错。

* 自动配置之后要自定义配置需编码javaconfig,需要了解这些配置类api。

* 版本迭代太快,新版本对老版本改动太多导致不兼容,比如1.3.5之前的springboottest和1.4.0之后的springboottest。

只有合适的架构才是最好的架构如果能接受spring boot这些缺点,spring boot确实是一个可以提高开发效率的不错的选择。

启动流程

扯了这么多,该上正题了,让我们来看看spring boot是怎样启动和启动做了哪些事情。

以下代码是spring boot项目标准的启动方式,使用注解@springbootapplication并且在main方法中调用springapplication的run方法,就可以完成。我们就从这个run方法开始看看spring boot的启动过程。

@springbootapplication
public class application {
  public static void main(string[] args){
    springapplication.run(application.class,args);
  }
}

我们进入run方法,可以看到最终是调用了 new springapplication(sources).run(args);new springapplication(sources).run(args); 这个方法,可以看到,springboot的启动可以分为两个部分,第一部分:springapplication的实例化;第二部分:调用该实例运行run方法。我们先来看看这个springapplication的实例化过程。

private void initialize(object[] sources) {
    if (sources != null && sources.length > 0) {
      this.sources.addall(arrays.aslist(sources));
    }
    //判定是否为webenvironment
    this.webenvironment = deducewebenvironment();
    //实例化并加载所有可以加载的applicationcontextinitializer
    setinitializers((collection) getspringfactoriesinstances(
        applicationcontextinitializer.class));
    //实例化并加载所有可以加载的applicationlistener
    setlisteners((collection)
    getspringfactoriesinstances(applicationlistener.class));
    this.mainapplicationclass = deducemainapplicationclass();
  }

关键点在两个set方法上**

setinitializers((collection) getspringfactoriesinstances(

applicationcontextinitializer.class))** 和 **setlisteners((collection)

getspringfactoriesinstances(applicationlistener.class))** 这两个方法一毛一样,挑实例化applicationcontextinitializer讲一讲。

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
    //使用loadfactorynames方法载入所有的applicationcontextinitializer的类全限定名
    set<string> names = new linkedhashset<string>(
        springfactoriesloader.loadfactorynames(type, classloader));
    //使用反射将所有的applicationcontextinitializer实例化   
    list<t> instances = createspringfactoriesinstances(type, parametertypes,
        classloader, args, names);
    //排序    
    annotationawareordercomparator.sort(instances);
    return instances;
  }

自动配置的关键就是这个 getspringfactoriesinstances方法,确切的说是这个方法里的loadfactorynames方法,浪我们看看这个loadfactorynames方法干了啥,咋就能实现自动配置。

public static list<string> loadfactorynames(class<?> factoryclass, classloader classloader) {
    string factoryclassname = factoryclass.getname();
    try {
      enumeration<url> urls = classloader != null?classloader.getresources("meta-inf/spring.factories"):classloader.getsystemresources("meta-inf/spring.factories");
      arraylist result = new arraylist();
      while(urls.hasmoreelements()) {
        url url = (url)urls.nextelement();
        properties properties = propertiesloaderutils.loadproperties(new urlresource(url));
        string factoryclassnames = properties.getproperty(factoryclassname);
        result.addall(arrays.aslist(stringutils.commadelimitedlisttostringarray(factoryclassnames)));
      }
      return result;
    } catch (ioexception var8) {
      throw new illegalargumentexception("unable to load [" + factoryclass.getname() + "] factories from location [" + "meta-inf/spring.factories" + "]", var8);
    }
  }

可以看到这个方法就做了一件事,就是从meta-inf/spring.factories这个路径取出所有”url”来,我们可以去到这个路径下看看到底是些啥?

# initializers
org.springframework.context.applicationcontextinitializer=\
org.springframework.boot.autoconfigure.logging.autoconfigurationreportlogginginitializer
# application listeners
org.springframework.context.applicationlistener=\
org.springframework.boot.autoconfigure.backgroundpreinitializer

这下大家都应该明白了,spring是通过将所有你加载的jar包中找到它需要的applicationcontextinitializer来进行动态的配置的,只要你有用到特定的maven包,初始化的时候会找这个包下的meta-inf/spring.factories的需要的类比如applicationcontextinitializer进行实例化bean,你就可以用了,不需要任何配置。

说到这已经将所有springapplication实例化说完了,只是在加载完applicationcontextinitializer和applicationlistener这之后还有一步,就是找到启动类所在的位置并且设入属性mainapplicationclass中。

接下来让我们回到new springapplication(sources).run(args)方法来看看run方法是怎么run的。

public configurableapplicationcontext run(string... args) {
    //开启启动计时器,项目启动完会打印执行时间出来
    stopwatch stopwatch = new stopwatch();
    stopwatch.start();
    configurableapplicationcontext context = null;
    failureanalyzers analyzers = null;
    configureheadlessproperty();
    //获取springapplicationrunlistener并启动监听器
    springapplicationrunlisteners listeners = getrunlisteners(args);
    listeners.starting();
    try {
      applicationarguments applicationarguments = new defaultapplicationarguments(
          args);
      //环境变量的加载    
      configurableenvironment environment = prepareenvironment(listeners,
          applicationarguments);
      //启动后console的打印出来的一堆配置信息    
      banner printedbanner = printbanner(environment);
      //终极大boss->applicationcontext实例化
      context = createapplicationcontext();
      analyzers = new failureanalyzers(context);
      preparecontext(context, environment, listeners, applicationarguments,
          printedbanner);
      refreshcontext(context);
      afterrefresh(context, applicationarguments);
      listeners.finished(context, null);
      stopwatch.stop();
      if (this.logstartupinfo) {
        new startupinfologger(this.mainapplicationclass)
            .logstarted(getapplicationlog(), stopwatch);
      }
      return context;
    }
    catch (throwable ex) {
      handlerunfailure(context, listeners, analyzers, ex);
      throw new illegalstateexception(ex);
    }
  }

从这个方法里面做的最关键的三件事情就是:

获取监听器并启动

加载环境变量,该环境变量包括system environment、classpath environment和用户自己加的application.properties

创建applicationcontext

private void preparecontext(configurableapplicationcontext context,
      configurableenvironment environment, springapplicationrunlisteners listeners,
      applicationarguments applicationarguments, banner printedbanner) {
    context.setenvironment(environment);
    postprocessapplicationcontext(context);
    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 = getsources();
    assert.notempty(sources, "sources must not be empty");
    load(context, sources.toarray(new object[sources.size()]));
    listeners.contextloaded(context);
  }

前两点没什么好说的,重点说说第三个,创建applicationcontext。创建applicationcontext又分为几部:实例化applicationcontext、preparecontext、refreshcontext。实例化applicationcontext会根据在之前我们说的webenvironment这个属性判断是使用webcontext类annotationconfigembeddedwebapplicationcontext还是普通context类annotationconfigapplicationcontext(在这里我们使用的是webcontext为例)然后通过反射进行实例化。applicationcontext实例化完了会进入preparecontext流程,这个preparecontext方法会加载之前准备好的environment进入context中,然后如果有beannamegenerator和resourceloader那么提前创建bean加载进applicationcontext,但是一般这两个都是空的,所以直接进入applyinitializers方法,将之前实例化的所有initializers进行初始化,所有的bean就是在这里进行bean的扫描和加载的因这次讲的是启动过程,所以不再细讲。最后把创建好的applicationcontext设置进入listener,preparecontext过程就结束了。最后是refreshcontext,这个就和spring的bean加载过程一致了,bean的注入、beanfactory、postprocessbeanfactory等等,详情可以去看看spring bean的生命周期。

总结

spring boot 初始化内容还是很多的,但是总结起来就四点:

* 创建springapplication实例,判定环境,是web环境还是普通环境。加载所有需要用到的initializers和listeners,这里使用约定大于配置的理念揭开了自动配置的面纱。

* 加载环境变量,环境变量包括system environment、classpath environment、application environment(也就是我们自定义的application.properties配置文件)

* 创建springapplicationrunlisteners

* 创建applicationcontext,设置装配context,在这里将所有的bean进行扫描最后在refreshcontext的时候进行加载、注入。最终将装配好的context作为属性设置进springapplicationrunlisteners,这就完成了一个spring boot项目的启动。

以上所述是小编给大家介绍的spring boot启动流程,希望对大家有所帮助