当前位置: 移动技术网 > IT编程>开发语言>Java > SpringBoot之旅第六篇-启动原理及自定义starter

SpringBoot之旅第六篇-启动原理及自定义starter

2019年04月29日  | 移动技术网IT编程  | 我要评论

一、引言

springboot的一大优势就是starter,由于springboot有很多开箱即用的starter依赖,使得我们开发变得简单,我们不需要过多的关注框架的配置。

在日常开发中,我们也会自定义一些starter,特别是现在微服务框架,我们一个项目分成了多个单体项目,而这些单体项目中会引用公司的一些组件,这个时候我们定义starter,可以使这些单体项目快速搭起,我们只需要关注业务开发。

在此之前我们再深入的了解下springboot启动原理。而后再将如何自定义starter。

二、 启动原理

要想了解启动原理,我们可以debug模式跟着代码一步步探究,我们从入口方法开始:

public static configurableapplicationcontext run(class<?>[] primarysources,
      string[] args) {
   return new springapplication(primarysources).run(args);
}

这里是创建一个springapplication对象,并调用了run方法

2.1 创建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));
    //确定web应用类型
    this.webapplicationtype = webapplicationtype.deducefromclasspath();
    //从类路径下找到meta-inf/spring.factories配置的所有applicationcontextinitializer;然后保存起来
    setinitializers((collection) getspringfactoriesinstances(
          applicationcontextinitializer.class));
    //从类路径下找到eta-inf/spring.factories配置的所有applicationlistener
    setlisteners((collection) getspringfactoriesinstances(applicationlistener.class));
    //从多个配置类中找到有main方法的主配置类
    this.mainapplicationclass = deducemainapplicationclass();
}

从这个方法中可以看出,这个

第一步:保存主配置类。

第二步:确定web应用类型。

第三步:setinitializers方法,这个方法走我们看带入的参数是getspringfactoriesinstances(applicationcontextinitializer.class),我们再往下查看getspringfactoriesinstances

再进入这个方法:

这里就是从类路径下找到meta-inf/spring.factories配置的所有applicationcontextinitializer,然后再保存起来,放开断点,我们可以看到这个时候获取到的

第四步:从类路径下找到eta-inf/spring.factories配置的所有applicationlistener,原理也基本类似,进入断点

第五步:从多个配置类中找到有main方法的主配置类。这个执行完之后,springapplication就创建完成

2.2 run方法

先贴出代码

public configurableapplicationcontext run(string... args) {
   stopwatch stopwatch = new stopwatch();
   stopwatch.start();
   configurableapplicationcontext context = null;
   collection<springbootexceptionreporter> exceptionreporters = new arraylist<>();
   configureheadlessproperty();
   //从类路径下meta-inf/spring.factories获取springapplicationrunlisteners
   springapplicationrunlisteners listeners = getrunlisteners(args);
   //回调所有的获取springapplicationrunlistener.starting()方法
   listeners.starting();
   try {
      //封装命令行参数
      applicationarguments applicationarguments = new defaultapplicationarguments(
            args);
       //准备环境 
      configurableenvironment environment = prepareenvironment(listeners,
            applicationarguments);//创建环境完成后回调springapplicationrunlistener.environmentprepared();表示环境准备完成
      configureignorebeaninfo(environment);
      //打印banner图
      banner printedbanner = printbanner(environment);
      //创建applicationcontext,决定创建web的ioc还是普通的ioc  
      context = createapplicationcontext();
       //异常分析报告
      exceptionreporters = getspringfactoriesinstances(
            springbootexceptionreporter.class,
            new class[] { configurableapplicationcontext.class }, context);
      //准备上下文环境,将environment保存到ioc中
      //applyinitializers():回调之前保存的所有的applicationcontextinitializer的initialize方法 
      //listeners.contextprepared(context) 
      //preparecontext运行完成以后回调所有的springapplicationrunlistener的contextloaded()
      preparecontext(context, environment, listeners, applicationarguments,
            printedbanner);
       //刷新容器,ioc容器初始化(如果是web应用还会创建嵌入式的tomcat)
       //扫描,创建,加载所有组件的地方,(配置类,组件,自动配置)
      refreshcontext(context);       
      afterrefresh(context, applicationarguments);
      stopwatch.stop();
      if (this.logstartupinfo) {
         new startupinfologger(this.mainapplicationclass)
               .logstarted(getapplicationlog(), stopwatch);
      }
      //所有的springapplicationrunlistener回调started方法
      listeners.started(context);
      //从ioc容器中获取所有的applicationrunner和commandlinerunner进行回调,
      //applicationrunner先回调,commandlinerunner再回调
      callrunners(context, applicationarguments);
   }
   catch (throwable ex) {
      handlerunfailure(context, ex, exceptionreporters, listeners);
      throw new illegalstateexception(ex);
   }

   try {
       //所有的springapplicationrunlistener回调running方法
      listeners.running(context);
   }
   catch (throwable ex) {
      handlerunfailure(context, ex, exceptionreporters, null);
      throw new illegalstateexception(ex);
   }
   //整个springboot应用启动完成以后返回启动的ioc容器
   return context;
}

前面的代码不用分析,主要是准备对象,我们从 springapplicationrunlisteners listeners = getrunlisteners(args)开始分析,

第一步:是从类路径下meta-inf/spring.factories获取springapplicationrunlisteners,

这个方法跟前面分析的两个获取配置方法类似。

第二步:回调所有的获取springapplicationrunlistener.starting()方法。

第三步: 封装命令行参数。

第四步:准备环境,调用prepareenvironment方法。

第五步:打印banner图(就是启动时的标识图)。

第六步:创建applicationcontext,决定创建web的ioc还是普通的ioc。

第七步:异常分析报告。

第八步:准备上下文环境,将environment保存到ioc中,这个方法需要仔细分析下,我们再进入这个方法

这里面有一个applyinitializers方法,这里是回调之前保存的所有的applicationcontextinitializer的initialize方法

还有一个listeners.contextprepared(context),这里是回调所有的springapplicationrunlistener的contextprepared(),

最后listeners.contextloaded(context) 是preparecontext运行完成以后回调所有的springapplicationrunlistener的contextloaded()。

第九步:刷新容器,ioc容器初始化(如果是web应用还会创建嵌入式的tomcat),这个就是扫描,创建,加载所有组件的地方,(配置类,组件,自动配置)。

第十步:所有的springapplicationrunlistener回调started方法。

第十一步:从ioc容器中获取所有的applicationrunner和commandlinerunner进行回调,applicationrunner先回调,commandlinerunner再回调。

第十二步:所有的springapplicationrunlistener回调running方法。

第十三步:整个springboot应用启动完成以后返回启动的ioc容器。

这就是run的全部过程,想要更详细的了解还需自己去看源码。

三、自定义starter

自定义starter(场景启动器),我们要做的事情是两个:确定依赖和编写自动配置。我们重点要做的就是编写自动配置,我们之前写过一些自动配置,主要是注解配置的使用,主要的注解有:

  • @configuration :指定这个类是一个配置类

  • @conditionalonxxx :在指定条件成立的情况下自动配置类生效

  • @autoconfigureafter:指定自动配置类的顺序

  • @bean:给容器中添加组件

  • @configurationpropertie:结合相关xxxproperties类来绑定相关的配置

  • @enableconfigurationproperties:让xxxproperties生效加入到容器中

按照这些注解写好自动配置类后,我们还需要进行自动配置的加载,加载方式是将需要启动就加载的自动配置类,配置在meta-inf/spring.factories,启动器的大致原理是如此,而启动器的实际设计是有一定模式的,就是启动器模块是一个空 jar 文件,仅提供辅助性依赖管理,而自动配置模块应该再重新设计一个,然后启动器再去引用这个自动配置模块。springboot就是如此设计的:

另外还有一个命名规则:

官方命名空间

– 前缀:“spring-boot-starter-”

– 模式:spring-boot-starter-模块名

– 举例:spring-boot-starter-web、spring-boot-starter-actuator、spring-boot-starter-jdbc

自定义命名空间

– 后缀:“-spring-boot-starter”

– 模式:模块-spring-boot-starter

– 举例:mybatis-spring-boot-starter

3.1 创建自定义starter

第一步:因为我们需要创建两个模块,所以先新建一个空的项目,然后以模块形式创建两个模块。

第二步:再创建两个模块,一个starter和一个自动配置模块

具体的创建过程就不赘述了,就是最简单的项目,去掉不需要的文件,创建完成结构如下:

第三步:我们先将自动配置模块导入starter中,让启动模块依赖自动配置模块

启动模块的pom文件加入依赖

<dependencies>
    <!--引入自动配置模块-->
    <dependency>
        <groupid>com.yuanqinnan-starter</groupid>
        <artifactid>yuanqinnan-springboot-starter-autoconfigurer</artifactid>
        <version>0.0.1-snapshot</version>
    </dependency>
</dependencies>

自动配置模块的完整pom文件:

<?xml version="1.0" encoding="utf-8"?>
<project xmlns="http://maven.apache.org/pom/4.0.0" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
         xsi:schemalocation="http://maven.apache.org/pom/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelversion>4.0.0</modelversion>
    <parent>
        <groupid>org.springframework.boot</groupid>
        <artifactid>spring-boot-starter-parent</artifactid>
        <version>2.1.4.release</version>
        <relativepath/> <!-- lookup parent from repository -->
    </parent>
    <groupid>com.yuanqinnan-starter</groupid>
    <artifactid>yuanqinnan-springboot-starter-autoconfigurer</artifactid>
    <version>0.0.1-snapshot</version>
    <packaging>jar</packaging>
    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!--引入spring-boot-starter;所有starter的基本配置-->
        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter</artifactid>
        </dependency>
    </dependencies>
</project>

至此,两个项目基本创建完成,现在我们实现简单的配置。

第五步:对自动配置类进行自动配置代码编写

先编写一个配置类,用于配置:

@configurationproperties(prefix = "yuanqinnan.hello")
public class helloproperties {
    //前缀
    private string prefix;
    //后缀
    private string suffix;

    public string getprefix() {
        return prefix;
    }

    public void setprefix(string prefix) {
        this.prefix = prefix;
    }

    public string getsuffix() {
        return suffix;
    }

    public void setsuffix(string suffix) {
        this.suffix = suffix;
    }
}

再编写一个服务

public class helloservice {

    helloproperties helloproperties;

    public helloproperties gethelloproperties() {
        return helloproperties;
    }

    public void sethelloproperties(helloproperties helloproperties) {
        this.helloproperties = helloproperties;
    }

    public string sayhello(string name) {
        return helloproperties.getprefix() + "-" + name + helloproperties.getsuffix();
    }
}

然后再将这个服务注入组件:

@configuration
@conditionalonwebapplication //web应用才生效
@enableconfigurationproperties(helloproperties.class)
public class helloserviceautoconfiguration {

    @autowired
    helloproperties helloproperties;
    @bean
    public helloservice helloservice(){
        helloservice service = new helloservice();
        service.sethelloproperties(helloproperties);
        return service;
    }
}

这个时候我们的自动配置以及写完,还差最后一步,因为springboot读取自动配置是在meta-inf的spring.factories文件中,所以我们还要将我们的自动配置类写入其中

org.springframework.boot.autoconfigure.enableautoconfiguration=\
  com.yuanqinnan.starter.helloserviceautoconfiguration

最后的结构如下:

至此,代码以及编写完成,这个时候我们将其装入仓库中,让其他项目引用

3.2 使用自定义starter

创建一个web项目,然后在项目中引入依赖

<!--引入自定义starter-->
<dependency>
  <groupid>com.yuanqinnan.starter</groupid>
  <artifactid>yuanqinnan-springboot-starter</artifactid>
  <version>1.0-snapshot</version>
</dependency>

在application.properties 配置中加上配置:

yuanqinnan.hello.prefix=早安
yuanqinnan.hello.suffix=晚安

加入测试:

@autowired
helloservice helloservice;
​
@test
public void contextloads() {
    system.out.println(helloservice.sayhello("世界"));
}

这样自定义starter和引用自定义都已完成,springboot的核心知识已经总结完成,后面再进行springboot的一些高级场景整合,如缓存、消息、检索、分布式等。

 

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网