Loading

1 2

【04】SpringBoot2核心技术-核心功能—高级特性_SpringBoot原理

6、高级特性

6.1 Profile功能

​ 为了方便多环境适配,比如在测试环境和生产环境中的切换,Springboot简化了profile功能。

1、application-profile功能

​ 在SpringBoot中,默认的配置文件是以application命名的properties或者yaml文件,针对不同的开发环境的相关配置,可以使用 application-***.yaml/yml/properties 的命名方式。

2022-01-02_185953

  • application.properties是默认的配置文件

  • 如果需要切换到其他开发环境,使用其他开发环境下的配置文件,那么需要在application.properties中进行如下设置:

    # 使用test环境
    spring.profiles.active=test
    
  • 其他环境的命名application-***.yaml,例如test、prod

    • test环境配置:

      person:
        name: test-张三
        age: 18
        
      server:
        port: 8000
      
    • prod环境配置:

      person:
        name: prod-张三
        age: 20
      
      server:
        port: 8088
      
    • 默认配置环境:指定使用test环境

      spring.profiles.active=test
      
    • 测试类:

      @Slf4j
      @RestController
      public class HelloController {
      
          @Value("${person.name:李四}")
          private String name;
      
          @Value("${person.age:10}")
          private String age;
      
          @GetMapping("/")
          public String hello(){
              log.info(name + "今年" + age + "岁");
              return "hello " + name;
          }
      }
      
    • 效果:控制台显示激活了test环境,并且初始化端口号为8000,输出效果也如下所示:

      2022-01-02_191905

      2022-01-02_191920

      2022-01-02_191927

  • 默认配置与环境配置同时生效,但修改配置文件的任意值时,环境配置优先

  • 激活指定的环境:

    • 配置文件激活

    • 命令行激活:java -jar xxx.jar --spring.profiles.active=prod --person.name=haha

    • 修改配置文件的任意值,命令行优先

2、@Profile条件装配功能

​ 指定在什么环境下才生效,进行装配:

例如:在prod环境下,才对Worker类进行自动装配,从prod环境下的配置类中取person的值,并放入容器中

@Configuration(proxyBeanMethods = false)
@Profile("prod")
@ConfigurationProperties("person")
@Component
public class Worker implements Person {

    private String name;
    private String age;
    
}

​ 例子:

@Configuration
public class MyConfig {

    @Profile("prod")
    @Bean
    public Color red(){
        return new Color();
    }

    @Profile("test")
    @Bean
    public Color green(){
        return new Color();
    }
}

3、Profile分组(多环境分组使用)

​ 当有多个配置环境文件需要被激活使用时,就可以使用profile分组。

​ 例:创建一个myprod的数组,里面含有ppd、prod两个环境配置文件,我们只需要激活myprod,就可以同时激活使用ppd、prod配置文件。

spring.profiles.active=myprod

spring.profiles.group.myprod[0]=ppd
spring.profiles.group.myprod[1]=prod

spring.profiles.group.mytest[0]=test

6.2 外部化配置

​ 抽取一部分可变配置,形成一个文件,方便集中管理,比如数据库连接的相关信息等。(properties、yaml文件)

https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config

1、外部配置源

常用:Java属性文件YAML文件环境变量命令行参数

优先级:命令行 > 环境变量 > 配置文件,下图优先级中,数字越大优先级越高

2022-01-02_194447

2、配置文件查找位置与顺序

(1) classpath(resources文件夹) 根路径

(2) classpath 根路径下config目录

(3) jar包当前目录

(4) jar包当前目录的config目录

(5) /config子目录的直接子目录

2022-01-02_194732

注意:配置文件的优先级的大小,从下往上,越往下优先级越高,会覆盖同名配置项。例如:类路径下的config文件夹中的application.yml文件优先级比类路径下的要高,即会使用config文件夹下的配置文件中的内容。

3、配置文件加载顺序

  1. 当前jar包内部的application.properties和application.yml

  2. 当前jar包内部的application-{profile}.properties 和 application-{profile}.yml

  3. 引用的外部jar包的application.properties和application.yml

  4. 引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml

加载顺序从上往下加载,因此最后在外部的配置文件会进行覆盖。

4、总结

指定环境优先,外部优先,后面的可以覆盖前面的同名配置项的值。

6.3 自定义starter(重要)

1、starter启动原理

  • pom文件中引入某个starter,进入starter后可以发现,是引入其他的一些依赖,最重要的是引入相关的 autoconfigurer 包

    2022-01-02_201124

  • autoconfigure包中配置使用 META-INF/spring.factoriesEnableAutoConfiguration 的值,使得项目启动加载指定的自动配置类

    注意是autoconfigure包下的

    2022-01-04_171406

  • 编写自动配置类 xxxAutoConfiguration -> xxxxProperties

    • @Configuration
    • @Conditional
    • @EnableConfigurationProperties
    • @Bean
    • ......

引入starter --- xxxAutoConfiguration --- 容器中放入组件 ---- 绑定xxxProperties ---- 配置项

2、自定义starter

boot-09-customer-starter(启动器)

boot-09-customer-starter-autoconfigure(自动配置包)

2022-01-04_165834

在boot-09-customer-starter中引入autoconfigure

(1)starter-autoconfigure 自动配置包

负责向容器导入组件

  • 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 https://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.6.2</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.atguigu</groupId>
        <artifactId>boot-09-customer-starter-autoconfigure</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>boot-09-customer-starter-autoconfigure</name>
        <description>boot-09-customer-starter-autoconfigure</description>
        <properties>
            <java.version>11</java.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
        </dependencies>
    
    </project>
    
  • bean:bean对象与配置文件中的数据进行绑定

    @ConfigurationProperties(prefix = "atguigu.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;
        }
    }
    
  • service:业务逻辑操作

    public class HelloService { // 默认不放在容器中
    
        @Autowired
        HelloProperties helloProperties; //在主配置类时,已经导入过组件了
    
        public String sayHello(String userName){
            return helloProperties.getPrefix() + ": " + userName + "》" + helloProperties.getSuffix();
        }
    }
    
  • auto:负责将组件导入容器中,当容器中没有HelloService这个组件时,就导入一个新的组件

    @Configuration
    // 向容器导入HelloProperties组件,无需在HelloProperties.class使用@Component注解修饰
    @EnableConfigurationProperties(HelloProperties.class) // HelloProperties会默认与指定前缀的数据进行绑定,并添加到容器中
    public class HelloServiceAutoConfiguration {
    
        @Bean
        @ConditionalOnMissingBean(HelloService.class)
        public HelloService helloService(){
            return new HelloService();
        }
    }
    
  • 在类路径下创建META-INF文件夹,创建spring.factories文件,指定项目启动后加载哪个自动配置类

    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.atguigu.auto.HelloServiceAutoConfiguration
    
(2)starter 启动器

主要就是一个包的依赖的归纳,比如将一个开发Spring web应用所需的依赖准备好,并导入,起一个引导作用。

  • pom文件:注意dependencies导入了自动配置包

    <?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 https://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.6.2</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.atguigu</groupId>
        <artifactId>boot-09-customer-starter</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>boot-09-customer-starter</name>
        <description>boot-09-customer-starter</description>
        <properties>
            <java.version>11</java.version>
        </properties>
    
        <!-- 导入了新定义的自动配置包 -->
        <dependencies>
            <dependency>
                <groupId>com.atguigu</groupId>
                <artifactId>boot-09-customer-starter-autoconfigure</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>
        </dependencies>
        
    </project>
    
(3)向maven仓库引入

2022-01-04_170919

(4)测试
  • 在一个新测试项目中的pom文件中,引入我们创建的starter启动器依赖

    <dependency>
        <groupId>com.atguigu</groupId>
        <artifactId>boot-09-customer-starter</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
    
  • controller

    @RestController
    public class HelloControllerNewStarter {
    
        @Autowired
        HelloService helloService; //自动注入容器中已经有的HelloService组件(得益于spring.factories中的自动导入设置)
    
        @GetMapping("/sayHello")
        public String sayHello(){
            String s = helloService.sayHello("马云");
            return s;
        }
    }
    
  • 配置文件中设置数据

    atguigu.hello.prefix=Jack
    atguigu.hello.suffix=Ma
    

    2022-01-04_174614

7、SpringBoot原理

Spring原理【Spring注解】、SpringMVC原理、自动配置原理、SpringBoot原理

7.1 SpringBoot启动过程

启动类:

@SpringBootApplication
public class Boot09CorefeaturesProfileApplication {

    public static void main(String[] args) {
        SpringApplication.run(Boot09CorefeaturesProfileApplication.class, args);
    }

}

1、创建SpringApplication

2022-01-03_224354

/**
*	创建一个新的SpringApplication实例(创建springboot应用)。应用程序上下文将从指定的主要来源加载 bean(有关详细信息,请参阅class-level文档)。可以在调用run(String...)之
*	前自定义实例。
*  参数:
*	resourceLoader – 要使用的资源加载器
*	primarySources – 主要的 bean 来源(启动类)
*/
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null"); //断言,判断有无启动类
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath(); //判断当前是响应式编程还是原生
    this.bootstrapRegistryInitializers = new ArrayList<>(
        getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); //获取启动引导器、IOC容器、监听器
    this.mainApplicationClass = deduceMainApplicationClass();
}
  • 运行Spring应用程序,创建一个新的 SpringApplication(启动引导器、容器初始化器、应用监听器三个)

    2022-01-03_195950

    进入SpringApplication的构造器方法:

    2022-01-03_200034

    • 保存启动类的信息(PrimarySources

    2022-01-02_211240

    • 这里就能发现primarySources就是指的SpringBoot的启动类(主类)

      2022-01-02_211423

    • 判定当前应用的类型,通过ClassUtils进行判断(是响应式编程还是原生Servlet)。这里当前是Servlet

      2022-01-02_211733

      2022-01-02_211816

    • 接下来是对三个模块的初始化操作:

    2022-01-02_211950

    • bootstrappers:初始启动引导器(List):去spring.factories文件中找 org.springframework.boot.Bootstrapper

      		this.bootstrapRegistryInitializers = new ArrayList<>(
      				getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
      
    • ApplicationContextInitializer(容器初始化器);去 spring.factoriesApplicationContextInitializer

      • List<ApplicationContextInitializer<?>> initializers
    • ApplicationListener 应用监听器。spring.factoriesApplicationListener

      • List<ApplicationListener<?>> listeners

2、运行SpringApplication

2022-01-03_224052

2022-01-03_224354

public ConfigurableApplicationContext run(String... args) {
    long startTime = System.nanoTime(); //记录起始时间
    DefaultBootstrapContext bootstrapContext = createBootstrapContext(); // 获得启动引导器,对上下文环境进行设置
    ConfigurableApplicationContext context = null;
    configureHeadlessProperty(); //当前应用进入headless模式
    SpringApplicationRunListeners listeners = getRunListeners(args); //获取所有运行监听器,为了保证监听器对当前应用状态进行感知
    listeners.starting(bootstrapContext, this.mainApplicationClass); //通知所有正在监听应用的监听器,项目正在启动
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 保存参数
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); //准备上下文环境(对环境进行配置,环境参数绑定等)
        configureIgnoreBeanInfo(environment); // 需要忽略的bean
        Banner printedBanner = printBanner(environment); //打印banner(SpringBoot应用启动时的banner)
        context = createApplicationContext(); // 创建IOC容器
        context.setApplicationStartup(this.applicationStartup);
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); // 传入容器所需数据
        refreshContext(context); // 刷新容器,将剩余所有组件添加到容器中
        afterRefresh(context, applicationArguments);
        Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime); //记录时间
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
        }
        listeners.started(context, timeTakenToStartup); //通知所有监听器,应用已经启动
        callRunners(context, applicationArguments); //调用所有的runner,便于应用启动的时候运行一些特定的代码(ApplicationRunner、CommandLineRunner)
    }
    catch (Throwable ex) { // 监控应用启动过程中,以上操作是否有报错
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }
    try {
        Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
        listeners.ready(context, timeTakenToReady); //通知所有监听器,springboot应用准备就绪,可以接收请求
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, null);
        throw new IllegalStateException(ex);
    }
    return context; // 返回上下文
}
  • 运行 SpringApplication(run)

    2022-01-03_200057

    2022-01-03_200321

    • 进入public ConfigurableApplicationContext run(String... args) {
    • 记录应用的启动时间

      long startTime = System.nanoTime();
      
    • 创建引导上下文(Context环境)createBootstrapContext()

      DefaultBootstrapContext bootstrapContext = createBootstrapContext();
      
      • 获取到之前所有的引导器 bootstrappers ,并遍历挨个执行 intitialize()完成对引导启动器上下文环境设置

        2022-01-03_200857

    • 让当前应用进入headless模式。java.awt.headless

      2022-01-03_201118

      private void configureHeadlessProperty() {
          System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
                             System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
      }
      
    • getRunListeners(args) 获取所有运行监听器 RunListener为了方便所有Listener进行事件感知

      2022-01-03_201318

      • 通过getRunListeners(args)方法中的getSpringFactoriesInstances方法,去spring.factoriesSpringApplicationRunListener

        2022-01-03_201649

        2022-01-03_201627

    • 接着往下执行,调用listeners.starting(),遍历 SpringApplicationRunListener 调用 starting 方法;

      listeners.starting(bootstrapContext, this.mainApplicationClass);
      
    • 2022-01-03_202239

    • 2022-01-03_202258

      • 相当于通知所有正在监听系统启动过程的进程,springboot应用正在启动 starting。
    • 保存命令行参数;ApplicationArguments

      2022-01-03_212015

    • 下一步,准备环境,调用 prepareEnvironment()方法;

      ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
      
    • 2022-01-03_212455

      • 返回或者创建基础环境信息对象。ApplicationServletEnvironment(实现了StandardServletEnvironment接口)

        ConfigurableEnvironment environment = getOrCreateEnvironment();
        

        2022-01-03_212541

      • 配置环境信息对象。

        configureEnvironment(environment, applicationArguments.getSourceArgs());//传入了环境信息、命令行参数信息
        
        • 读取所有的配置源的配置属性值。
      • 绑定环境信息

        ConfigurationPropertySources.attach(environment);
        
      • 监听器listeners调用 listener.environmentPrepared()通知所有的监听器当前环境准备完成

        listeners.environmentPrepared(bootstrapContext, environment);
        
    • 接下来,就是一系列环境信息的初始化和绑定,基础环境准备部分结束,最后返回环境对象

      2022-01-03_214716

    • 基础环境创建完成后,接着是忽略一些bean信息,以及打印banner

      2022-01-03_214929

      2022-01-03_215138

    • (重要)创建IOC容器 createApplicationContext()

      • 根据项目类型(Servlet)创建容器

        2022-01-03_215240

      • 所以当前会创建容器: AnnotationConfigServletWebServerApplicationContext

        2022-01-03_215318

    • 准备ApplicationContext IOC容器的基本信息;调用prepareContext()方法

      prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
      
    • 进入prepareContext():准备IOC容器的方法

    • 注意:每当容器准备就绪、组件添加完成、加载完毕后,都要通知监听器相应阶段已经完成

    • 2022-01-03_215656

      • 保存环境信息:context.setEnvironment(environment);
      • IOC容器的后置处理流程:postProcessApplicationContext(context);
      • 应用初始化器:applyInitializers(context);
        • 遍历所有的 ApplicationContextInitializer 。调用 initialize.。来对ioc容器进行初始化扩展功能

          2022-01-03_220003

        • 遍历所有的 listener ,调用 contextPrepared()方法进行遍历。EventPublishRunListenr来通知所有的监听器:容器上下文已经准备完毕(容器中并没有实例)

      • 将启动所需的组件添加到容器中:比如banner

        2022-01-03_220839

      • 所有的监听器又调用 contextLoaded。通知所有的监听器 contextLoaded,也就是context(IOC容器)已经加载完成了

        2022-01-03_221159

    • 刷新IOC容器:refreshContext(context);

      run方法当前已经执行到的位置如下:

      2022-01-03_221434

      • 一直step into就能到达最底层的refresh()方法,用来创建容器中的所有组件(往容器中加组件实例)
    • try {
          // Allows post-processing of the bean factory in context subclasses.
          postProcessBeanFactory(beanFactory);
      
          StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
          // Invoke factory processors registered as beans in the context.
          invokeBeanFactoryPostProcessors(beanFactory);
      
          // Register bean processors that intercept bean creation.
          registerBeanPostProcessors(beanFactory);
          beanPostProcess.end();
      
          // Initialize message source for this context.
          initMessageSource();
      
          // Initialize event multicaster for this context.
          initApplicationEventMulticaster();
      
          // Initialize other special beans in specific context subclasses.
          onRefresh();
      
          // Check for listener beans and register them.
          registerListeners();
      
          // Instantiate all remaining (non-lazy-init) singletons.
          finishBeanFactoryInitialization(beanFactory);
      
          // Last step: publish corresponding event.
          finishRefresh();
      }
      
    • 容器刷新完成后工作:afterRefresh(context, applicationArguments);

    • 容器创建所需时间:Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);

    • 通知所有监听器,springboot应用已经启动了,并通知这一过程的所需时间:listeners.started(context, timeTakenToStartup); ,此时容器中有所有组件的实例。

    • 调用所有runners;callRunners(context, applicationArguments);

      如果你想在Spring Boot启动的时候运行一些特定的代码,你可以实现接口 ApplicationRunner或者 CommandLineRunner,这两个接口实现方式一样,它们都只提供了一个run方法。

      2022-01-03_222527

      2022-01-03_222618

      • 获取容器中的 ApplicationRunner:启动获取命令行参数
      • 获取容器中的 CommandLineRunner:启动获取应用启动的时候参数
      • 合并所有runner并且按照@Order进行排序
      • 遍历所有的runner。调用 run 方法
    • 有一个异常监听器,如果以上有异常,就会调用Listener 的 failed
    • 2022-01-03_223505
    • 如果没有异常的话,就又会通知所有监听器,springboot应用准备就绪,可以接收请求,ready

      try {
          Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
          listeners.ready(context, timeTakenToReady);
      }
      
    • **running如果有问题,继续通知 failed **

      • 调用所有 Listener 的failed;通知所有的监听器 failed
      catch (Throwable ex) {
          handleRunFailure(context, ex, null);
          throw new IllegalStateException(ex);
      }
      
    • 就绪,返回容器:上下文context

      2022-01-03_223942

posted @ 2022-01-04 22:06  Komorebi_WH  阅读(221)  评论(0编辑  收藏  举报