SpringBoot基础回顾-9

**SpringBoot starter机制**

​      SpringBoot由众多Starter组成(一系列的自动化配置的starter插件),SpringBoot之所以流行,也是因为starter。

starter是SpringBoot非常重要的一部分,可以理解为一个可拔插式的插件,正是这些starter使得使用某个功能的开发者不需要关注各种依赖库的处理,不需要具体的配置信息,由Spring Boot自动通过classpath路径下的类发现需要的Bean,并织入相应的Bean。

例如,你想使用Reids插件,那么可以使用spring-boot-starter-redis;如果想使用MongoDB,可以使用spring-boot-starter-data-mongodb

**为什么要自定义starter**

开发过程中,经常会有一些独立于业务之外的配置模块。如果我们将这些可独立于业务代码之外的功能配置模块封装成一个个starter,复用的时候只需要将其在pom中引用依赖即可,SpringBoot为我们完成自动装配

**自定义starter的命名规则**

SpringBoot提供的starter以`spring-boot-starter-xxx`的方式命名的。官方建议自定义的starter使用`xxx-spring-boot-starter`命名规则。以区分SpringBoot生态提供的starter

整个过程分为两部分:

- 自定义starter

- 使用starter

首先,先完成自定义starter

(1)新建maven  jar工程,工程名为zdy-spring-boot-starter,导入依赖:

```xml

<dependencies>


<dependency>


<groupId>org.springframework.boot</groupId>


<artifactId>spring-boot-autoconfigure</artifactId>


<version>2.2.2.RELEASE</version>


</dependency>

</dependencies>

```

(2)编写javaBean

```java

@EnableConfigurationProperties(SimpleBean.class)

@ConfigurationProperties(prefix =

"simplebean")

public class SimpleBean {


private int id;


private String name;


public int getId() {


return id;

    }


public void setId(int id) {


this.id = id;

    }


public String getName() {


return name;

    }


public void setName(String name) {


this.name = name;

    }


@Override


public String toString() {


return "SimpleBean{" +

                "id=" + id +

                ", name='" + name +

'\'' +

                '}';

    }

}

```

(3)编写配置类MyAutoConfiguration

```java

@Configuration

@ConditionalOnClass //@ConditionalOnClass:当类路径classpath下有指定的类的情况下进行自动配置

public class MyAutoConfiguration {


static {


System.out.println("MyAutoConfiguration init....");

    }


@Bean


public SimpleBean simpleBean(){


return new SimpleBean();

    }

}

```

(4)resources下创建/META-INF/spring.factories

注意:META-INF是自己手动创建的目录,spring.factories也是手动创建的文件,在该文件中配置自己的自动配置类

<img

src="./images/image-20200111123116471.png"

alt="image-20200111123116471" style="zoom:67%;" />

```factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

com.lagou.config.MyAutoConfiguration

```

使用自定义starter

(1)导入自定义starter的依赖

```xml

<dependency>


<groupId>com.lagou</groupId>


<artifactId>zdy-spring-boot-starter</artifactId>


<version>1.0-SNAPSHOT</version>

</dependency>

```

(2)在全局配置文件中配置属性值

```properties

simplebean.id=1

simplebean.name=自定义starter

```

(3)编写测试方法

```java

//测试自定义starter

@Autowired

private SimpleBean simpleBean;

@Test

public void zdyStarterTest(){


System.out.println(simpleBean);

}

```

### 

2.4 执行原理

​       

每个Spring Boot项目都有一个主程序启动类,在主程序启动类中有一个启动项目的main()方法,在该方法中通过执行SpringApplication.run()即可启动整个Spring Boot程序。

问题:那么SpringApplication.run()方法到底是如何做到启动Spring Boot项目的呢?

下面我们查看run()方法内部的源码,核心代码具体如下:

```java

@SpringBootApplication

public class SpringbootDemoApplication {


public static void main(String[] args) {


SpringApplication.run(SpringbootDemoApplication.class, args);

  }

}

```

```java

public static

ConfigurableApplicationContext run(Class<?> primarySource,

String... args) {

      return

run(new Class[]{primarySource}, args);

}

public static ConfigurableApplicationContext

run(Class<?>[] primarySources,

String[] args) {

      return

(new SpringApplication(primarySources)).run(args);

}

```

​       

从上述源码可以看出,SpringApplication.run()方法内部执行了两个操作,分别是SpringApplication实例的初始化创建和调用run()启动项目,这两个阶段的实现具体说明如下

**1.SpringApplication实例的初始化创建**

​       

查看SpringApplication实例对象初始化创建的源码信息,核心代码具体如下 

```java

public SpringApplication(ResourceLoader

resourceLoader, Class... primarySources) {

      this.sources

= new LinkedHashSet();

      this.bannerMode

= Mode.CONSOLE;

      this.logStartupInfo

= true;

      this.addCommandLineProperties

= true;

      this.addConversionService

= true;

      this.headless

= true;

      this.registerShutdownHook

= true;

      this.additionalProfiles

= new HashSet();

      this.isCustomEnvironment

= false;

      this.resourceLoader

= resourceLoader;

      Assert.notNull(primarySources,

"PrimarySources must not be null");

  //把项目启动类.class设置为属性存储起来

      this.primarySources

= new LinkedHashSet(Arrays.asList(primarySources));


  //判断当前webApplicationType应用的类型

      this.webApplicationType

= WebApplicationType.deduceFromClasspath();


  // 设置初始化器(Initializer),最后会调用这些初始化器

      this.setInitializers(this.getSpringFactoriesInstances(

ApplicationContextInitializer.class));

  // 设置监听器(Listener)

      this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));

  // 用于推断并设置项目main()方法启动的主程序启动类

      this.mainApplicationClass

= this.deduceMainApplicationClass();

```

从上述源码可以看出,SpringApplication的初始化过程主要包括4部分,具体说明如下。

(1)this.webApplicationType

= WebApplicationType.deduceFromClasspath()

用于判断当前webApplicationType应用的类型。deduceFromClasspath()方法用于查看Classpath类路径下是否存在某个特征类,从而判断当前webApplicationType类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring

5开始出现的WebFlux交互式应用) 

(2)this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class))

用于SpringApplication应用的初始化器设置。在初始化器设置过程中,会使用Spring类加载器SpringFactoriesLoader从META-INF/spring.factories类路径下的META-INF下的spring.factores文件中获取所有可用的应用初始化器类ApplicationContextInitializer。

(3)this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class))

用于SpringApplication应用的监听器设置。监听器设置的过程与上一步初始化器设置的过程基本一样,也是使用SpringFactoriesLoader从META-INF/spring.factories类路径下的META-INF下的spring.factores文件中获取所有可用的监听器类ApplicationListener。

(4)this.mainApplicationClass

= this.deduceMainApplicationClass()

用于推断并设置项目main()方法启动的主程序启动类

**2.项目的初始化启动**

​       

分析完(new

SpringApplication(primarySources)).run(args)源码前一部分SpringApplication实例对象的初始化创建后,查看run(args)方法执行的项目初始化启动过程,核心代码具体如下:

```java

public ConfigurableApplicationContext

run(String... args) {

      StopWatch

stopWatch = new StopWatch();

      stopWatch.start();

      ConfigurableApplicationContext

context = null;

      Collection<SpringBootExceptionReporter>

exceptionReporters = new ArrayList();

      this.configureHeadlessProperty();


// 第一步:获取并启动监听器

      SpringApplicationRunListeners

listeners = this.getRunListeners(args);

      listeners.starting();

      Collection

exceptionReporters;

      try

{

              ApplicationArguments

applicationArguments =

new DefaultApplicationArguments(args);


// 第二步:根据SpringApplicationRunListeners以及参数来准备环境

              ConfigurableEnvironment

environment =

this.prepareEnvironment(listeners,

applicationArguments);

              this.configureIgnoreBeanInfo(environment);


// 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体

              Banner

printedBanner = this.printBanner(environment);


              //

第三步:创建Spring容器

              context

= this.createApplicationContext();

              exceptionReporters

=

                    this.getSpringFactoriesInstances(SpringBootExceptionReporter.class,

new

Class[]{ConfigurableApplicationContext.class}, new Object[]{context});



// 第四步:Spring容器前置处理

this.prepareContext(context, environment,

listeners,

applicationArguments, printedBanner);



// 第五步:刷新容器

              this.refreshContext(context);



// 第六步:Spring容器后置处理

              this.afterRefresh(context,

applicationArguments);

              stopWatch.stop();

              if(this.logStartupInfo)

{

                    (new

StartupInfoLogger(this.mainApplicationClass))

.logStarted(this.getApplicationLog(),

stopWatch);

              }


// 第七步:发出结束执行的事件

              listeners.started(context);



// 返回容器

              this.callRunners(context,

applicationArguments);

      }

catch (Throwable var10) {

              this.handleRunFailure(context,

var10, exceptionReporters, listeners);

              throw

new IllegalStateException(var10);

      }

      try

{

              listeners.running(context);

              return

context;

      }

catch (Throwable var9) {

              this.handleRunFailure(context,

var9, exceptionReporters,

(SpringApplicationRunListeners)null);

              throw

new IllegalStateException(var9);

      }

}

```

从上述源码可以看出,项目初始化启动过程大致包括以下部分:

- 第一步:获取并启动监听器

```

this.getRunListeners(args)和listeners.starting()方法主要用于获取SpringApplication实例初始化过程中初始化的SpringApplicationRunListener监听器并运行。

```

- 第二步:根据SpringApplicationRunListeners以及参数来准备环境

```

this.prepareEnvironment(listeners,

applicationArguments)方法主要用于对项目运行环境进行预设置,同时通过this.configureIgnoreBeanInfo(environment)方法排除一些不需要的运行环境

```

- 第三步:创建Spring容器

```

根据webApplicationType进行判断,      确定容器类型,如果该类型为SERVLET类型,会通过反射装载对应的字节码,也就是AnnotationConfigServletWebServerApplicationContext,接着使用之前初始化设置的context(应用上下文环境)、environment(项目运行环境)、listeners(运行监听器)、applicationArguments(项目参数)和printedBanner(项目图标信息)进行应用上下文的组装配置,并刷新配置

```

- 第四步:Spring容器前置处理

```

这一步主要是在容器刷新之前的准备动作。设置容器环境,包括各种变量等等,其中包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础

```

- 第五步:刷新容器

```

开启刷新spring容器,通过refresh方法对整个IOC容器的初始化(包括bean资源的定位,解析,注册等等),同时向JVM运行时注册一个关机钩子,在JVM关机时会关闭这个上下文,除非当时它已经关闭

```

- 第六步:Spring容器后置处理

```

扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。

```

- 第七步:发出结束执行的事件

```

获取EventPublishingRunListener监听器,并执行其started方法,并且将创建的Spring容器传进去了,创建一个ApplicationStartedEvent事件,并执行ConfigurableApplicationContext

的publishEvent方法,也就是说这里是在Spring容器中发布事件,并不是在SpringApplication中发布事件,和前面的starting是不同的,前面的starting是直接向SpringApplication中的监听器发布启动事件。

```

- 第八步:执行Runners

```

用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序。其中,Spring Boot提供的执行器接口有ApplicationRunner 和CommandLineRunner两种,在使用时只需要自定义一个执行器类实现其中一个接口并重写对应的run()方法接口,然后Spring Boot项目启动后会立即执行这些特定程序

```

***上了拉勾教育的《Java工程师高薪训练营》,做一下笔记。希望拉勾能给我推到想去的公司,目标:字节!!***

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335