SpringBoot根底

从Spring到SpringBoot

  • Spring通过IOCAOP实现了企业级的开发框架,尽管组件代码是轻量级的,然而配置文件却是重量级的,Spring Boot则简化Spring利用开发,基于约定大于配置(为大部分配置组件提供了默认配置)的思维,just run 就能创立一个独立的,产品级别的利用

    • SpringBoot框架并不是微服务框架,只是为微服务框架的组件构建提供了一个很好的脚手架
    • SpringBoot提供了J2EE一站式解决方案;Spring Cloud提供了分布式整体解决方案

Spring生态

  • Spring严格来说是一个生态,而非仅仅几个常见的框架,基本上笼罩了以下几个次要场景的开发

    • web开发

      • Spring Framework
    • 数据拜访

      • Spring Data
    • 安全控制

      • Spring Security
    • 分布式

      • Spring Cloud
      • Spring Session
      • ..
    • 音讯服务

      • Spring AMQP
    • 批处理

      • Spring Batch
      在大型企业中,因为业务简单、数据量大、数据格式不同、数据交互格局繁冗,并非所有的操作都能通过交互界面进行解决。而有一些操作须要定期读取大批量的数据,而后进行一系列的后续解决。这样的过程就是批处理
    • ...
  • 能够说Spring以一己之力晋升了整个Java的开发生态,使得Java语言在支流开发场景中都能大显神通,正如Spring官网的首页图展现的那样

    <img src="https://images.demoli.xyz/image-20210924220823740.png" alt="image-20210924220823740" style="zoom:67%;" />

SpringBoot存在的意义

  • 一般来说,Spring生态的学习路线都是从SSM(Spring、Spring MVC、MyBatis)到SpringBoot,SSM中的配置天堂是升高开发效率的微小阻碍;除此之外,上述说的Spring生态的泛滥性能的搭建与配合应用同样意味着大量的配置,是否简化配置,甚至做到主动配置(约定大于配置的思维),以晋升业务开发效率?SpringBoot应运而生。具体的劣势在以下几个方面:

    • 能够创立独立的Spring利用
    • 内嵌Web服务器
    • 基于starter的场景主动配置
    • 主动配置Spring与第三方性能
    • 提供生产级别的监控、健康检查,内部化配置
    • 无代码生成,无需编写XML
  • SpringBoot的毛病

    • 版本迭代快
    • 封装比拟深,查看源码难度比拟高

SpringBoot2.0

  • 作为SpringBoot的新一代版本,SpringBoot2.0在整个框架结构设计上都有重大降级,次要包含以下两局部:

响应式编程

<img src="https://images.demoli.xyz/image-20210924223917397.png" alt="image-20210924223917397" />

  • 提出了响应式技术栈,即以异步非阻塞的形式使得整个框架可能以更少的系统资源解决更多的并发申请,对应的底层Web开发框架是Spring WebFlux,而不是传统的同步阻塞式的SpringMVC,这一部分可参考后续对于[Spring WebFlux的学习介绍]()

源码设计调整

  • 齐全基于JDK8构建,同时兼容JDK11甚至Java17,基于一些新的Java个性对外部源码进行了从新设计,比方JDK8之前,接口没有默认办法实现的特色,导致接口的实现类不得不实现全副的接口办法,即使只应用其中几个办法,为了解决此问题,Spring中大量应用的适配器模式,应用适配器实现接口办法为空办法,再让子类去重写办法,而在JDK8后,一个default关键字就解决了,实现类无必要实现全副办法,因而大量的adapter就隐没了

HelloWorld我的项目

环境配置

  • 首先依照SpringBoot文档要求保障JDK版本为8或以上版本,Spring Framework版本、maven以及gradle版本参考要求即可

    • 可参考具体版本的SpringBoot文档的Getting Started页面,比方2.6.5版本

我的项目实现

  1. IDEA创立maven我的项目
  2. 引入starters

    <parent>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-parent</artifactId>  <version>2.5.5</version></parent><dependencies>  <dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-web</artifactId>  </dependency></dependencies>
    • 父工程对立治理可能应用到的各个组件的版本
    • 导入Web场景启动器以实现Web利用开发
  3. 创立入口类与Controller

    @SpringBootApplicationpublic class MainApplication {    public static void main(String[] args) {        SpringApplication.run(MainApplication.class, args);    }}
    @RestControllerpublic class HelloController {    @RequestMapping("/hello")    public String handleHello() {        return "Hello SpringBoor 2";    }}
    • 与Spring MVC开发的代码编写统一,最大的不同在于无需增加大量的配置
    • 留神业务代码的包应和入口类在同一个包下,否则无奈解析业务代码中的的注解配置
  4. 启动运行

    1. 间接启动入口程序即可开启内置的服务器提供Web服务,实现本地测试
  5. 自定义配置

    server:  port: 8888
    • 我的项目类门路(maven工程的resources目录下)增加名为application.yml配置文件,既能够做自在的配置
    • 所有可做的配置参考官网文档
  6. 部署,应用SpringBoot提供的打包为可执行jar包的形式

     <!-- 这个插件,能够将利用打包成一个可执行的jar包;--><build>  <plugins>    <plugin>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-maven-plugin</artifactId>    </plugin>  </plugins></build>
    • 执行mvn package即可取得打包好的jar文件,在指标服务器执行java -jar ${可执行jar包门路}即可运行整个工程
    • 在IDEA中可能显示红色划线谬误示意找不到此plugin,应该是IDEA的一个bug

SpringBoot的两个特色

  • 以HelloWorld我的项目为例钻研SpringBoot的两个个性

    • 依赖治理
    • 自动化配置(约定大于配置)

依赖治理

  • 实质上就是应用maven父工程和starter启动器做依赖治理

    • maven父工程做依赖版本治理
    • starter场景启动器做依赖包治理
Maven父工程
  • spring-boot-starter-parent-2.5.5.pom如下所示,能够发现spring-boot-starter-parent设置了默认的Java编译版本是8,因而如果我的项目应用更高版本的话,须要在我的项目的pom文件中显式设置<java.version>这个properties
<parent>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-dependencies</artifactId>    <version>2.5.5</version></parent><artifactId>spring-boot-starter-parent</artifactId><packaging>pom</packaging><name>spring-boot-starter-parent</name><description>Parent pom providing dependency and plugin management for applications built with Maven</description><properties>  <java.version>1.8</java.version>  <resource.delimiter>@</resource.delimiter>  <maven.compiler.source>${java.version}</maven.compiler.source>  <maven.compiler.target>${java.version}</maven.compiler.target>  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding></properties>
  • spring-boot-dependencies-2.5.5.pom
<groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.5.5</version><packaging>pom</packaging><properties>    <activemq.version>5.16.3</activemq.version>    <antlr2.version>2.7.7</antlr2.version>    <appengine-sdk.version>1.9.91</appengine-sdk.version>    <artemis.version>2.17.0</artemis.version>    <aspectj.version>1.9.7</aspectj.version>    <assertj.version>3.19.0</assertj.version>    <atomikos.version>4.0.6</atomikos.version>    <awaitility.version>4.0.3</awaitility.version>    <build-helper-maven-plugin.version>3.2.0</build-helper-maven-plugin.version>    <byte-buddy.version>1.10.22</byte-buddy.version>    <caffeine.version>2.9.2</caffeine.version>    <cassandra-driver.version>4.11.3</cassandra-driver.version>    <classmate.version>1.5.1</classmate.version>  ....</properties><dependencyManagement>    <dependencies>      <dependency>        <groupId>org.apache.activemq</groupId>        <artifactId>activemq-amqp</artifactId>        <version>${activemq.version}</version>      </dependency>      ...  </dependencies></dependencyManagement>
  • 在pom文件中找到最终的父工程,即spring-boot-dependencies,这个父我的项目中定义了一堆properties,其中规定了简直所有的支流组件的版本,所以这个spring-boot-dependencies用来治理SpringBoot我的项目中的依赖版本,是SpringBoot利用的版本管理中心,之后的各种援用依赖就不须要再注明版本号了

    • 实际上利用的是maven的版本继承的机制
  • 上述的机制也叫做SpringBoot中的版本仲裁机制,也是一种约定大于配置的思维的体现,就是当时依照版本匹配的约定做好配置,放弃版本的统一,而不须要手动再配置,然而如果有非凡的版本需要或者是其余的不存在与版本管理中心的非凡依赖,则间接在以后的SpringBoot Maven工程显式的指定版本或者依赖即可

    • 例如,如果只是更改版本的话,只须要在我的项目pom文件中,应用property标签设置版本号即可

      <properties>  <mysql.version>5.1.43</mysql.version></properties>
  • 其余的SpringBoot maven更具体的配置参考
  • 如果应用Gradle搭建我的项目,参考

    • 以搭建一般Web利用为例,其配置文件如下所示:

      plugins {    id 'org.springframework.boot' version '2.6.5'    id 'io.spring.dependency-management' version '1.0.11.RELEASE'    id 'java'}group = 'xyz.demoli'version = '0.0.1-SNAPSHOT'sourceCompatibility = '11'repositories {    mavenCentral()}dependencies {    implementation 'org.springframework.boot:spring-boot-starter-web'    testImplementation 'org.springframework.boot:spring-boot-starter-test'}tasks.named('test') {    useJUnitPlatform()}
      • 能够发现Gradle以Plugin的模式提供了依赖版本治理
场景启动器
  • 因为要开发Web利用所以在HelloWorld我的项目中引入了一个spring-boot-starter-web依赖,在后续的SpringBoot应用中会用到很多所谓的spring-boot-starter-*依赖,也就是场景启动器依赖
  • 场景启动器的意义在于能够将某利用场景的所有可能用到的依赖打包到一起,实现只须要引入一个启动器依赖就能够引入全副场景依赖的目标,简化依赖导入的流程,以spring-boot-starter-web为例,如下图所示,tomcat、Spring MVC、Spring等相干场景的依赖被对立打包在starter内导入到我的项目中

  • 启动器除了打包依赖,当然也负责这些依赖的版本治理。启动器给出了所有依赖的适配版本,如果我的项目应用默认提供的版本能够同样在pom文件中进行显式定制

    <groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.5.5</version><dependencies>  <dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter</artifactId>    <version>2.5.5</version>    <scope>compile</scope>  </dependency>  <dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-json</artifactId>    <version>2.5.5</version>    <scope>compile</scope>  </dependency>  <dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-tomcat</artifactId>    <version>2.5.5</version>    <scope>compile</scope>  </dependency>  <dependency>    <groupId>org.springframework</groupId>    <artifactId>spring-web</artifactId>    <version>5.3.10</version>    <scope>compile</scope>  </dependency>  <dependency>    <groupId>org.springframework</groupId>    <artifactId>spring-webmvc</artifactId>    <version>5.3.10</version>    <scope>compile</scope>  </dependency></dependencies>
  • 场景启动器不仅蕴含了特定开发场景须要的所有依赖还蕴含反对该场景的组件默认配置,具体参考下边的主动配置原理局部
  • 能够自定义场景启动器以满足定制化的开发需要,可参考文章[SpringBoot Starter开发]()

    • 个别本人开发的或者是第三方的starter的名称都是*-spring-boot-starter
  • SpringBoot官网反对的starters

主动配置原理

  • 以HelloWorld我的项目的Web利用开发场景为例
从程序入口看起
  • 入口main办法中的SpringApplicationrun办法是整个程序的理论入口,此办法可实现Spring环境的创立,包含各种Bean的注册以及后续要介绍的各种注解的解析与作用
  • 入口main办法所在的入口类上有一个要害的注解,即@SpringBootApplication被这个注解标注的类是SpringBoot的主配置类,执行这个类的main办法来启动SpringBoot利用
  • 该注解实际上是一个组合注解

    @Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(    excludeFilters = {@Filter(    type = FilterType.CUSTOM,    classes = {TypeExcludeFilter.class}), @Filter(    type = FilterType.CUSTOM,    classes = {AutoConfigurationExcludeFilter.class})})public @interface SpringBootApplication {  @AliasFor(        annotation = EnableAutoConfiguration.class    )    Class<?>[] exclude() default {};    @AliasFor(        annotation = EnableAutoConfiguration.class    )    String[] excludeName() default {};    @AliasFor(        annotation = ComponentScan.class,        attribute = "basePackages"    )    String[] scanBasePackages() default {};    @AliasFor(        annotation = ComponentScan.class,        attribute = "basePackageClasses"    )    Class<?>[] scanBasePackageClasses() default {};    @AliasFor(        annotation = Configuration.class    )    boolean proxyBeanMethods() default true;}
    • 留神@AliasFor注解的作用是申明被标记的SpringBootApplication注解的属性与@AliasForannotation属性申明的注解的同名属性或者是attribute指定的属性是同一个属性
SpringBootConfiguration注解
  • 用来指定作用的入口类是Spring配置类,在包扫描后失效

    @Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Configurationpublic @interface SpringBootConfiguration {    @AliasFor(        annotation = Configuration.class    )    boolean proxyBeanMethods() default true;}
    @SpringBootApplicationpublic class SpringBootHelloWorldQuickApplication {    public static void main(String[] args) {        ConfigurableApplicationContext context = SpringApplication.run(SpringBootHelloWorldQuickApplication.class, args);        SpringBootHelloWorldQuickApplication application = context.getBean("springBootHelloWorldQuickApplication", SpringBootHelloWorldQuickApplication.class);        System.out.println(application);    }}
    • 能够从IoC容器中获取主配置类Bean
  • 只管启动类同时被ComponentScan注解和Configuration注解作用,然而ComponentScan做注解解析时,不会因为启动类被Configuration作用,而反复的去解析启动类上的注解,这一点在ComponentScanAnnotationParser的parse办法中有定义,该办法中增加了一个AbstractTypeHierarchyTraversingFilter该filter的match办法会过滤掉启动类

    public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {    // ...    ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry, componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);  // ...  scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {    protected boolean matchClassName(String className) {      return declaringClass.equals(className);    }  });  return scanner.doScan(StringUtils.toStringArray(basePackages));}
ComponentScan注解
  • 默认的,主程序所在的包以及所有的下层级的包都会被扫描解析

    • 也能够应用@SpringBootApplication注解的scanBasePackages属性指定自定义的包扫描地位
    • 或者间接应用@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan三个注解代替@SpringBootApplication,而后再应用@ComponentScan配置包扫描门路即可
  • SpringBootApplication注解中的ComponentScan注解应用了两个主动扫描的排除规定

    @ComponentScan(    excludeFilters = {@Filter(    type = FilterType.CUSTOM,    classes = {TypeExcludeFilter.class}), @Filter(    type = FilterType.CUSTOM,    classes = {AutoConfigurationExcludeFilter.class})})public @interface SpringBootApplication {    // ...}
    • TypeExcludeFilter次要是做扩大应用,能够通过继承此类实现自定义扫描排除规定
    • AutoConfigurationExcludeFilter 设置不扫描所有的既是配置类又是主动配置类的组件,以避免出现反复注册
    • 参考1
    • 参考2
EnableAutoConfiguration注解
  • EnableAutoConfiguration注解顾名思义用来开启starter提供的主动配置(主动配置的性能是以主动配置类的模式作为Bean进行注册后提供),该注解实质上还是合成注解

    @Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import({AutoConfigurationImportSelector.class})public @interface EnableAutoConfiguration {    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";    Class<?>[] exclude() default {};    String[] excludeName() default {};}
    • @AutoConfigurationPackage注解

      @Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Import({Registrar.class})public @interface AutoConfigurationPackage {}
      • Import注解作用的Registar类实现了ImportBeanDefinitionRegistrar接口,因而实际上会调用其registerBeanDefinitions办法

        static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {  Registrar() {  }  // 此办法被调用  public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {    AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());  }  public Set<Object> determineImports(AnnotationMetadata metadata) {    return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));  }}public static void register(BeanDefinitionRegistry registry, String... packageNames) {  if (registry.containsBeanDefinition(BEAN)) {    BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);    ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();    constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));  } else {    GenericBeanDefinition beanDefinition = new GenericBeanDefinition();    beanDefinition.setBeanClass(AutoConfigurationPackages.BasePackages.class);    beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);    beanDefinition.setRole(2);    registry.registerBeanDefinition(BEAN, beanDefinition);  }}
        • 通过断点剖析可得,实际上是将以后注解所标注的类(即入口类)所在包的门路封装到 org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages类型的Bean进行注册
    • @Import({AutoConfigurationImportSelector.class}),因为AutoConfigurationImportSelector类实现了DeferredImportSelector接口,所以该类会被作为配置类解析的最初一步,ConfigurationClassParser类的parse办法会调用AutoConfigurationImportSelector类的process办法

      public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {    // ...}
      public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {  //...  // 外围办法 getAutoConfigurationEntry  AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(this.getAutoConfigurationMetadata(), annotationMetadata);  //...}
      protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {  //...  // 外围办法是getCandidateConfigurations 通过该办法能够获取要注册的所有配置类汇合,后续的操作都是对此配置类汇合的  List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);  // 对主动配置类汇合做一些调整  configurations = this.removeDuplicates(configurations);  Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);  this.checkExcludedClasses(configurations, exclusions);  configurations.removeAll(exclusions);  configurations = this.filter(configurations, autoConfigurationMetadata);  this.fireAutoConfigurationImportEvents(configurations, exclusions);  return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);}}
       protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {   // 要害办法为loadFactoryNames   List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());   Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");   return configurations; }
      public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {  // org.springframework.boot.autoconfigure.EnableAutoConfiguration  String factoryTypeName = factoryType.getName();  // 要害办法 loadSpringFactories  return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());}
      private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {  // ...  try {    Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");    // ...}
      • 通过查看源码可知,实际上会扫描org.springframework.boot:spring-boot-autoconfigure包下的META-INF/spring.factories文件,会读取文件中的org.springframework.boot.autoconfigure.EnableAutoConfiguration项下的所有主动配置类

        • 实质上是扫描类门路下所有包内的META-INF/spring.factories文件,然而starter组件包一起其余依赖包内都没有META-INF/spring.factories文件,因而在自定义starter时,为了使自定义的默认配置失效,也须要向starter中照猫画虎的增加这么一个文件才行
      • getAutoConfigurationEntry办法对从getCandidateConfigurations中取得的所有主动配置类的汇合进行了肯定的批改,次要包含

        • 去重
        • 排除掉@EnableAutoConfiguration注解中的exclude和excludeName属性申明的配置类

          • getExclusions办法
        • 排除掉被条件拆卸注解作用的主动配置类中的不符合条件的局部

          • filter办法
          • 可参考下边的@Condition注解的原理局部
主动配置类
  • SpringBoot中所有的默认配置都被集中管理在一个我的项目中,即spring-boot-autoconfigure
  • 所有的SpringBoot的官网场景启动器都会有spring-boot-starter这个依赖,这个依赖的又有一个依赖就是spring-boot-autoconfigure。查看其源码,能够发现有各种场景下的默认配置类,个别这些默认配置类都会以**AutoConfiguration命名主动配置类在满足特定的条件后被注册到Spring容器中,则其主动配置能够在SpringBoot我的项目中失效

    • spring-boot-autoconfigure我的项目除了提供各个场景下的主动配置类,还提供了对应的配置类供用户实现自定义配置,以晋升灵活性。application.yaml配置文件中的可用配置都会映射到配置类**Properties中,**AutoConfiguration主动配置类会尝试加载注册到**Properties中的用户自定义配置,使其失效(如果有的话)

      // 以server.port=9000为例 对应的配置类是 org.springframework.boot.autoconfigure.web.ServerProperties@ConfigurationProperties(    prefix = "server",    ignoreUnknownFields = true)public class ServerProperties {    private Integer port;    private InetAddress address;    //...      public void setPort(Integer port) {        this.port = port;    }      //..}
      • 应用的要害注解是@ConfigurationProperties
配置的按需加载
  • 前边说到META-INF/spring.factories中定义的主动配置类不会全副都被注册失效,只会在特定条件下失效,这种按条件注册的性能依赖于下边的一系列注解

    // 以批处理的主动配置类为例@Configuration(    proxyBeanMethods = false)@ConditionalOnClass({JobLauncher.class, DataSource.class})@AutoConfigureAfter({HibernateJpaAutoConfiguration.class})@ConditionalOnBean({JobLauncher.class})@EnableConfigurationProperties({BatchProperties.class})@Import({BatchConfigurerConfiguration.class, DatabaseInitializationDependencyConfigurer.class})public class BatchAutoConfiguration {  //...}
    • 要害注解是@ConditionalOnClass@ConditionalOnBean,顾名思义,其要求注解属性中的类被导入后,才会使得BatchAutoConfiguration的配置失效,而JobLauncher等类只有在批处理的场景启动器依赖导入或者是相干的次要第三方依赖导入后才会被导入到工程中,由此实现主动配置的按需加载
@Conditional系列注解
@Conditional扩大注解作用(判断是否满足以后指定条件)
@ConditionalOnJava零碎的java版本是否符合要求
@ConditionalOnBean容器中存在指定Bean
@ConditionalOnMissingBean容器中不存在指定Bean
@ConditionalOnExpression满足SpEL表达式指定
@ConditionalOnClass零碎中有指定的类
@ConditionalOnMissingClass零碎中没有指定的类
@ConditionalOnSingleCandidate容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty零碎中指定的属性是否有指定的值
@ConditionalOnResource类门路下是否存在指定资源文件
@ConditionalOnWebApplication以后是web环境
@ConditionalOnNotWebApplication以后不是web环境
@ConditionalOnJndiJNDI存在指定项
  • 上边列举的都是能够间接拿来用的条件拆卸的注解,即@Conditional的扩大注解,@Conditional注解自身的应用就绝对繁琐了,须要本人定义判断逻辑

    @Component// 依据OnSmtpEnvCondition中的条件判断是否注册SmtpMailService@Conditional(OnSmtpEnvCondition.class)public class SmtpMailService implements MailService {    ...}
    // 实现Condition接口public class OnSmtpEnvCondition implements Condition {    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {        return "true".equalsIgnoreCase(System.getenv("smtp"));    }}
    • OnSmtpEnvCondition的条件是存在环境变量smtp,值为true。这样就能够通过环境变量来管制是否创立SmtpMailService
  • 这个系列的依赖能够作用在办法上,与@Bean一起应用,决定该办法的返回对象是否要注册到容器中,也能够作用在类上,与@Configuration一起应用,决定该配置类整体是否失效
  • ConditionalOnClass注解为例,实质上起作用的是OnClassCondition类,而该类实际上实现了AutoConfigurationImportFilter接口,通过其match办法判断条件是否失效,而match办法判断是否失效的过程则是在前文提到的AutoConfigurationImportSelector类的filter办法中实现的

    @Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documented@Conditional({OnClassCondition.class})public @interface ConditionalOnClass {    Class<?>[] value() default {};    String[] name() default {};}
    class OnClassCondition extends FilteringSpringBootCondition {    // ...}
    public interface AutoConfigurationImportFilter {    boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata);}
@Profile注解
  • 应用该注解定义不同环境下的条件拆卸

    • dev 开发环境
    • test 测试环境
    • prod 生产环境
    @Configuration@ComponentScanpublic class AppConfig {    @Bean    @Profile("!test")    ZoneId createZoneId() {        return ZoneId.systemDefault();    }    @Bean    @Profile("test")    ZoneId createZoneIdForTest() {        return ZoneId.of("America/New_York");    }}
    • 如果以后的Profile设置为test,则Spring容器会调用createZoneIdForTest()创立ZoneId,否则,调用createZoneId()创立ZoneId
  • 在运行程序时,加上JVM参数-Dspring.profiles.active=test就能够指定以test环境启动,也能够指定多个环境-Dspring.profiles.active=test,master
补充
  • 用户定制化配置的几种形式

    • 间接应用@Configuration + @Bean替换底层的组件

      • 底层大量应用了@ConditionOnMissingBean以优先应用用户注册的Bean
      • 举荐首先查看文档或者去大抵看看源码的配置要点
    • 通过看文档或者间接查看主动配置中的配置文件类,对应的向零碎配置文件application.yaml中增加自定义配置
    • 应用组件提供的customizer类或者configurer类

      • 通过查看文档或材料取得相干常识
  • 除了..AutoConfiguration系列的主动配置类与..Properties系列的配置文件类,SpringBoot中还有两种类型的类即:

    • ..Configurer是SpringBoot中的用于用户扩大配置的接口或者类,例如WebMvcConfigurer
    • ..Customizer用来进行定制配置,例如WebServerFactoryCustomizer
    • 参考[应用SpringBoot进行Web开发的文章]()
  • 语法提醒,通过增加下边的插件提供配置文件中的关联提醒的性能

    <build>  <plugins>    <plugin>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-maven-plugin</artifactId>      <configuration>        <excludes>          <groupId>org.springframework.boot</groupId>          <artifactId>spring-boot-configuration-processor</artifactId>        </excludes>      </configuration>    </plugin>  </plugins></build><dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-configuration-processor</artifactId>  <optional>true</optional></dependency>
    • 同时留神在打包插件中配置打包时不蕴含该依赖,因为只是开发时要用到,没必要将其打包
最佳实际
  • 依据需要引入官网或者第三方提供的starter

    • SpringBoot提供的所有官网starter
  • 个别都须要对场景做自定义配置,能够通过查官网的配置文档,或者间接从源码层面查看配置项

    • SpringBoot官网提供的所有可用配置
    • 依照需要批改配置,或者增加自定义组件
  • trick:在配置文件中应用debug=true以debug模式运行,控制台会有主动配置报告,能够看到失效的主动配置类与未失效的主动配置类以及其未失效的起因

    • Positive matches:这个栏目下是失效的主动配置类Negative matches:这个栏目下是未失效的主动配置类,以及未失效的起因(这一点能够用来做自定义配置失败的debug)
    • 如果引入了日志框架,能够将日志打印等径设置为debug,成果是相似的,当然更举荐上边的形式
  • 应用lombok优化Bean开发,SpringBoot曾经治理了其版本,因而间接引入maven依赖即可,而后在IDEA中装置lombok插件以优化应用体验

    • @Data 等价于@Getter@Setter组合
    • @Getter
    • @Setter
    • @ToString
    • @NoArgsConstructor
    • @AllArgsConstructor
    • @EqualsAndHashCode
    • @Log4j2

      • 间接为以后类注入一个org.apache.logging.log4j.Logger类型的名为log的属性,用来做日志打印
      • 须要留神的是必须首先增加log4j2的依赖
  • 应用devtools热更新性能,能够增加maven依赖也能够在创立我的项目时在Spring initializer中指定

    <dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-devtools</artifactId>  <scope>runtime</scope>  <optional>true</optional></dependency>
    • 此时只需rebuild就能够使更改失效,其工作机制本质上就是监听classpath下的文件的变动,一旦监听到变动就重启服务器加载变动
    • 除了能够监听classpath门路下的文件的变动,还能够监听自定义的目录下的文件变动,以达到文件变动后,服务器重新启动的目标,参照codesheep-SpringBoot热部署加持
  • 举荐应用Spring Initializer创立我的项目(IDEA自带)能够通过勾选的形式做一系列场景配置,生成的我的项目主动引入相干依赖以及写好主配置类,以及特定场景的对应的文件构造,比方Web场景下会创立类门路下的static文件夹和template文件夹等等

SpringBoot配置文件

配置文件

  • SpringBoot默认应用两种配置文件

    • application.properties
    • application.yml

Yaml语法

  • 介绍一下yaml的根本语法

    • 应用缩进示意层级关系
    • 缩进时不容许应用tab键,只容许应用空格
    • 缩进的空格数不重要,只有雷同层级的元素左侧对其即可
    • 大小写敏感
    • 示意数据应用k: v的模式展现 冒号后边必须要有一个空格
  • 数据类型

    • 字面量:字符串,数字,bool,date

      • 留神字符串类型的值,不须要加引号,如果加的话,''""别离对应着对其中的字符串中的转义字符进行字符串输入和本义输入

        • 当字符串中有特殊字符时,务必加引号
    • 对象,Map

      • 对象还是用键值对的模式来示意

        friends:    lastName: zhangsan    age: 20
      • 行内写法

        friends: {lastName: zhangsan,age: 20}
        • 留神行内写法中的对象属性冒号后边也得有空格
    • 数组

      • -示意数组中的一个元素

        pets: - cat - dog - pig
      • 行内写法

        pets: [cat,dog,pig]
    • 配置文件占位符

      server:  port: 8081bean:  # 随机数的拼接  lastName: hello${random.uuid}  age: ${random.int}  boss: false  birth: 1997/12/10  maps: {k1: v1,k2: v2}  lists: [lisi,zhaoliu]  dog:    # 应用:指定当属性不存在时的默认值    name: ${bean.Name:lee}_dog    # 复用前边的设置    lastName: ${bean.lastName:gee}    age: 2
      • 随机数占位符

        • random.value
        • random.int
        • random.int(int max)
        • random.long
      • 两种类型的配置文件都反对占位符的性能

Profile

  • Profile是Spring对多环境的反对,程序开发的时候有几个环节 开发-测试-部署,三个场景有三个环境,不同的环境下配置文件也可能不同,能够通过激活,指定参数等形式疾速切换环境,同时主动切换指定场景的配置文件

    • 为每一个环境指定一个配置文件,每一个配置文件对应一个profile,命名格局为application-{profile}.properties 例如:application-dev.properties application-prod.properties
    • 当工程中有多个配置文件的时候,默认加载的是不带profile的配置文件也就是application.properties/application.yml
yaml文档块
  • 对于properties类型的配置文件来说,须要定义多个配置文件对应不同的profile,然而对于yaml配置文件来说,能够把多个profile通过文档块的模式写到一个文件中
  • 所谓的文档块就是在yml配置文件中用---符号宰割为多个块,每一个块就对应着一个profile的配置文件

    server:  port: 8081spring:  profiles:    active: prod---server:  port: 8082spring:  profiles: dev---server:  port: 8083spring:  profiles: prod
    • 非默认配置文件文档块应用spring.profiles申明文档块属于哪个环境
环境切换形式
  • 默认的不指定profile的配置文件中进行激活

    spring:  profiles:    active: dev #profile名字
  • JVM参数

    • 在idea的edit configuration中配置vm arguments -Dspring.profiles.active=dev
    • 优先级高于配置文件
  • 命令行
    • 在idea的edit configuration中配置program arguments --spring.profiles.active=dev
    • 这种配置办法优先级最高
默认配置文件优先级
  • 所有地位的默认文件都会被加载,高优先级配置的内容会笼罩低优先级配置的内容,不反复的内容则是补充内容

    • root:./config/application.yaml
    • root:./application.yaml
    • classpath:/config/application.yaml
    • classpath:/application.yaml
  • 参考
内部配置文件
  • 所谓应用内部配置文件指的是对SpringBoot Application进行部署的时候能够指定包内部的配置文件,来代替外部的配置,以进步配置的自由度(配置批改后不用从新打包)
  • 依照优先级排列

    • 命令行中间接指定配置参数:java -jar ***.jar --server.port=8090 优先级最高
    • 命令行中指定配置文件:能够应用--spring.config.location=...指定配置文件的地位
    • jar包内部的application-{profile}.properties或者application.yml(带spring.profile)配置文件

      • 带profile的优先(profile的指定形式就是前边说的那三种)
      • jar包外指的是与jar包同目录的配置文件,会主动执行加载,不必配置其余命令行参数
    • jar包内部的application.properties或者application.yml(不带spring.profile)配置文件
    • jar包外部的application-{profile}.properties或者application.yml(带spring.profile)配置文件
    • jar包外部的application.properties或者application.yml(不带spring.profile)配置文件

      • 如果同时呈现则application.properties优先
  • 参考

SpringBoot事件监听

  • 所谓的事件监听就是指SprigBoot为利用启动的不同期间提供了能够监听的钩子组件,容许利用执行到特定的阶段时,执行注册好的钩子函数
  • 注册钩子函数的形式就是实现SpringBoot提供的特定的接口,而后注册成为Spring IoC中的组件或者是在以后利用的类门路下的META-INF文件夹下的spring.factories文件中进行配置,具体的看下边的例子

几个罕用的事件回调机制

  • 前三个须要配置在spring.factories中,后两个须要注册成为IoC组件

    • org.springframework.context.ApplicationContextInitializer

      public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {    void initialize(C var1);}
      public class HelloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {    @Override    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {        System.out.println("ApplicationContextInitializer initialize");    }}
      org.springframework.context.ApplicationContextInitializer=\com.springboot.springboot.listenerAndRunner.HelloApplicationContextInitializer
    • org.springframework.boot.SpringApplicationRunListener

      public interface SpringApplicationRunListener {    default void starting() {    }    default void environmentPrepared(ConfigurableEnvironment environment) {    }    default void contextPrepared(ConfigurableApplicationContext context) {    }    default void contextLoaded(ConfigurableApplicationContext context) {    }    default void started(ConfigurableApplicationContext context) {    }    default void running(ConfigurableApplicationContext context) {    }    default void failed(ConfigurableApplicationContext context, Throwable exception) {    }}
      public class HelloSpringApplicationRunListener implements SpringApplicationRunListener {    /**     * 须要提供此构造函数,否则报错     *     * @param application SpringApplication     * @param args        参数     */    public HelloSpringApplicationRunListener(SpringApplication application, String[] args) {    }    @Override    public void starting() {        System.out.println("SpringApplicationRunListener starting");    }    @Override    public void environmentPrepared(ConfigurableEnvironment environment) {        System.out.println("SpringApplicationRunListener environmentPrepared " + environment.getSystemProperties().get("os.name"));    }    @Override    public void contextPrepared(ConfigurableApplicationContext context) {        System.out.println("SpringApplicationRunListener contextPrepared");    }    @Override    public void contextLoaded(ConfigurableApplicationContext context) {        System.out.println("SpringApplicationRunListener contextLoaded");    }    @Override    public void started(ConfigurableApplicationContext context) {        System.out.println("SpringApplicationRunListener started");    }    @Override    public void running(ConfigurableApplicationContext context) {        System.out.println("SpringApplicationRunListener running");    }    @Override    public void failed(ConfigurableApplicationContext context, Throwable exception) {        System.out.println("SpringApplicationRunListener failed");    }}
      org.springframework.boot.SpringApplicationRunListener=\com.springboot.springboot.listenerAndRunner.HelloSpringApplicationRunListener
    • org.springframework.context.ApplicationListener

      @FunctionalInterfacepublic interface ApplicationListener<E extends ApplicationEvent> extends EventListener {    void onApplicationEvent(E var1);}
      public class HelloApplicationListener implements ApplicationListener<ApplicationPreparedEvent> {    @Override    public void onApplicationEvent(ApplicationPreparedEvent applicationPreparedEvent) {        System.out.println(applicationPreparedEvent.getTimestamp());    }}
      org.springframework.context.ApplicationListener=\com.springboot.springboot.listenerAndRunner.HelloApplicationListener
    • org.springframework.boot.ApplicationRunner

      @FunctionalInterfacepublic interface ApplicationRunner {    void run(ApplicationArguments args) throws Exception;}
      @Componentpublic class HelloApplicationRunner implements ApplicationRunner {    @Override    public void run(ApplicationArguments args) throws Exception {        System.out.println("ApplicationRunner run " + args);    }}
    • org.springframework.boot.CommandLineRunner

      @FunctionalInterfacepublic interface CommandLineRunner {    void run(String... args) throws Exception;}
      @Componentpublic class HelloCommandLineRunner  implements CommandLineRunner {    @Override    public void run(String... args) throws Exception {        System.out.println("CommandLineRunner run " + Arrays.toString(args));    }}

从启动流程剖析回调机制

  • 在SpringApplication的run办法上打断点剖析启动流程

    1. 创立SpringApplication对象 org.springframework.boot.SpringApplication#100

      public SpringApplication(Class<?>... primarySources) {  this((ResourceLoader)null, primarySources);}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.lazyInitialization = false;  this.resourceLoader = resourceLoader;  // 必须提供非空的主配置类,否则无奈启动  Assert.notNull(primarySources, "PrimarySources must not be null");  // 保留主配置类  this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));  // 判断web利用的类型,留神这里并不是默认就是web利用,实际上其外部会判断是不是web利用,如果不是会返回NONE,是web利用的话,也要辨别是哪种类型 1. REACTIVE 2,SERVLET  this.webApplicationType = WebApplicationType.deduceFromClasspath();  // 从类门路下的META-INF/spring.factories中取得org.springframework.context.ApplicationContextInitializer 并保存起来  this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));  //  同样的从类门路下的META-INF/spring.factories中取得配置的org.springframework.context.ApplicationListener 并保存起来  this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));  // 从多个配置类中找到含有main办法的入口类  this.mainApplicationClass = this.deduceMainApplicationClass();}
      • 程序入口的main办法未必肯定在主配置类上,因而在晓得主配置类的前提下寻找含有main办法的入口类并不抵触
      • 利用默认加载的ApplicationContextInitializer如下,红色标记为自定义

      • 利用默认加载的ApplicationListener如下,红色标记为自定义

    2. 执行run办法 org.springframework.boot.SpringApplication#138

      public ConfigurableApplicationContext run(String... args) {  StopWatch stopWatch = new StopWatch();  stopWatch.start();  // 申明一个ioc容器  ConfigurableApplicationContext context = null;  Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();  this.configureHeadlessProperty();  // 获取org.springframework.boot.SpringApplicationRunListener  // 从类门路的META-INF的spring.factories中取得配置好的runlisteners  SpringApplicationRunListeners listeners = this.getRunListeners(args);  // 回调执行所有SpringApplicationRunListener的starting办法  listeners.starting();  Collection exceptionReporters;  try {    // 封装命令行参数    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);    // 筹备环境,创立环境并回调执行SpringApplicationRunListener的environmentPrepared办法    ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);    this.configureIgnoreBeanInfo(environment);    // 打印spring的图标    Banner printedBanner = this.printBanner(environment);    // 创立ioc容器,并且依据前边的判断的web利用的类型来创立并返回不同类型的容器    context = this.createApplicationContext();    exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);    // 筹备上下文,将environment环境存储到容器中,并回调执行所有的保留的ApplicationContextInitializer的initialize办法, 执行SpringApplicationRunListeners的contextPrepared办法,在prepareContext函数执行的最初执行SpringApplicationRunListeners的contextLoaded办法    this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);    // 刷新环境 IOC容器初始化    // 如果是web利用的话,还会创立Tomcat容器,并启动Tomcat容器    // 扫描,创立加载 所有的组件(配置类,组件,主动配置都会在这里失效)    this.refreshContext(context);    // 空办法,可能只是为了与前几个版本进行兼容吧    this.afterRefresh(context, applicationArguments);    stopWatch.stop();    if (this.logStartupInfo) {      (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);    }    // 回调执行所有SpringApplicationRunListeners的started办法    listeners.started(context);    // 从IOC容器中取得ApplicationRunner,和CommandLineRunner,并先后进行回调其run办法    this.callRunners(context, applicationArguments);  } catch (Throwable var10) {    this.handleRunFailure(context, var10, exceptionReporters, listeners);    throw new IllegalStateException(var10);  }  try {    // 回调执行所有SpringApplicationRunListeners的running办法    listeners.running(context);    // 整个springboot利用启动实现当前返回ioc容器    return context;  } catch (Throwable var9) {    this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);    throw new IllegalStateException(var9);  }}
      • 须要留神,注册自定义的SpringApplicationRunListener时,除了实现SpringApplicationRunListener之外,还必须要提供特定构造的构造函数,否则会创立实例失败,这是因为在getRunListeners源码中是这样创立SpringApplicationRunListener实例的

        private SpringApplicationRunListeners getRunListeners(String[] args) {  Class<?>[] types = new Class[]{SpringApplication.class, String[].class};  // getSpringFactoriesInstances办法提供了this与args两个构造函数的参数  return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));}private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {  ClassLoader classLoader = this.getClassLoader();  Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));  List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);  AnnotationAwareOrderComparator.sort(instances);  return instances;}private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {  List<T> instances = new ArrayList(names.size());  Iterator var7 = names.iterator();  while(var7.hasNext()) {    String name = (String)var7.next();    try {      Class<?> instanceClass = ClassUtils.forName(name, classLoader);      Assert.isAssignable(type, instanceClass);      // 获取特定构造的构造函数以初始化,否则报错      Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);      T instance = BeanUtils.instantiateClass(constructor, args);      instances.add(instance);    } catch (Throwable var12) {      throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, var12);    }  }  return instances;}

总结执行机会

  1. 进入动态run办法
  2. 创立SpringApplication实例
  3. SpringApplication的实例run办法执行,申明IoC容器
  4. 执行SpringApplicationRunListener 的 starting办法
  5. 执行SpringApplicationRunListener 的 environmentPrepared办法
  6. 打印SpringBoot图标,创立IoC容器
  7. 执行ApplicationContextInitializer 的 initialize办法
  8. 执行SpringApplicationRunListener 的 contextPrepared办法
  9. 执行SpringApplicationRunListener 的 contextLoaded办法
  10. 执行refreshContext办法,初始化IoC容器,如果是Servlet Web利用,会依据配置创立对应的Servlet容器,比方Tomcat,并启动,除此之外,会扫描,创立,加载所有的组件(也包含主动配置的失效,即对应的一系列注解的失效)
  11. 执行SpringApplicationRunListener 的 started办法
  12. 执行ApplicationRunner 的 run办法
  13. 执行CommandLineRunner 的 run办法
  14. 执行SpringApplicationRunListener 的 running办法
  15. 至于 ApplicationListener,依据其泛型中申明的事件不同,其onApplicationEvent办法的执行工夫也不同,具体情况具体分析

参考

  1. SpringBoot官网文档
  2. SpringBoot更新日志
  3. B站视频材料

    1. 配套文档