SpringBoot高级特性

SpringBoot高级特性

Profile功能

为了方便多环境适配,SpringBoot简化了profile功能

application-profile功能

  • 默认配置文件 application.yaml,任何时候都会加载

  • 指定环境配置文件application-env.yaml

  • 激活指定环境

    • 配置文件激活

    • 命令行激活

      java -jar xxx.jar --spring.profiles.active=profile
      

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

  • 默认配置与环境配置同时生效

  • 同名配置项,profile配置优先

在配置文件中指定环境

spring:
  profiles:
    active: pro

@Profile条件装配

@Profile("pro")
@Component
@ConfigurationProperties(prefix = "person")
@Data
public class Boss implements Person {

  private String name;

  private Integer age;

}

@Profile("pro")当环境为pro时,这个类才会被注册到容器中。

profile分组

spring:
  profiles:
    active: diy
    group:
      diy:
        - dev
        - pro

分组之后可以使用spring.profiles.active将这个组中的配置环境全部加载。

外部化配置

外部配置源

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

配置文件查找位置

外部化配置文件可以从以下几个路径进行查找,后面加载的配置会覆盖前面的配置

  1. classpath根路径
  2. classpath根路径下config目录
  3. jar包当前目录
  4. jar包当前目录的lconfig目录
  5. config子目录的直接子目录

配置文件加载顺序

  1. 当前jar包内部的application.properties和application.yaml
  2. 当前jar包内部的application-.properties和application-.yaml
  3. 引用的外部jar包的application.properties和application.yaml
  4. 引用的外部jar包的application-.properties和application-.yaml

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

自定义starter

starter启动原理

  • starter-pom引入autoconfigurer包
    截屏2021-07-18 15.15.25

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

    # auto configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    top.ctong.springbootstartautoconfigure.auto.HelloServiceAutoConfiguration
    
  • 编写自动配置类xxxAutoConfigration -> xxxProperties

    • @Configuration
    • @Conditional
    • @EnableConfigurationProperties
    • @Bean
    @Configuration
    @ConditionalOnMissingBean(HelloService.class)// 如果之前通过奇奇怪怪的方法注册了一个service,那么就不使用这个配置类了
    @EnableConfigurationProperties(HelloProperties.class)// 当这个配置类生效后,自动将这个properties放在容器中,如果没有开启这个配置,那么在service中的自动注入可能会找不到指定Bean
    public class HelloServiceAutoConfiguration {
    
      @Bean
      public HelloService helloService() {
        HelloService service = new HelloService();
        return service;
      }
    }
    

操作过程

源码GitHub地址

新建一个空工程在指定文件夹中

截屏2021-07-18 16.56.14

新建成功一个空的工程后,会自动打开一个添加模块的面板,先需要添加一个starter工程「其实就是一个普通的maven工程」。

starter工程一般是空的,只有一些基本的结构,但这并不绝对。

截屏2021-07-18 16.58.02

完成之后可以根据需求删除不需要的文件夹,保留pom.xml文件即可。之后再新建一个模块,它是一个maven工程

起个名字吧!就叫spring-boot-hello-service-starter-autoconfigure

截屏2021-07-18 17.07.04

然后在customer-starter项目中引入刚刚创建的工程

<dependencies>
    <dependency>
        <groupId>top.ctong</groupId>
        <artifactId>spring-boot-hello-service-starter-autoconfigure</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>	
</dependencies>

在spring-boot-hello-service-starter-autoconfigure工程中创建top.ctong.springboothelloservicestarterautoconfigure.servlet.HelloServlet

top.ctong.springboothelloservicestarterautoconfigure.bean.HelloProperties

top.ctong.springboothelloservicestarterautoconfigure.HelloServiceAutoConfigration

创建这3个文件后在service中编写逻辑,例如我有一个请求,这个请求接受一个参数并读取配置文件中配置HelloProperties的内容。

需要HelloProperties绑定配置文件,但不用在这注入到容器中,此时会提示错误,忽略即可。

@ConfigurationProperties(prefix = "clover.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中的内容。此时HelloProperties已经和配置文件绑定,但还未注入到容器,所以执行时@Autowired会出错

public class HelloServlet {

    @Autowired
    private HelloProperties helloProperties;

    public String sayHello(String userName) {
        return helloProperties.getPrefix()+" : " + userName + " >> " + helloProperties.getSuffix();
    }
}

最后编写自动配置类

@Configuration
@EnableConfigurationProperties(HelloProperties.class)
@ConditionalOnMissingBean(HelloService.class)
public class HelloServiceAutoConfiguration implements Serializable {

  @Bean
  public HelloService helloService() {
    return new HelloService();
  }

}
  • @EnableConfigurationProperties(HelloProperties.class)

    当这个配置类生效后,将这个properties注入到容器中

  • @ConditionalOnMissingBean(HelloService.class)
    如果已经有一个不知道通过什么奇奇怪怪的手段注入到容器中的Bean,那么这个自动配置类就不生效

最后需要添加一个spring.factories开启这个自动配置类

# auto configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
top.ctong.springboothelloservicestarterautoconfigure.auto.HelloServiceAutoConfiguration

在resources/MATE-INF下

完成之后需要将我们编写的starter打包并安装到本地maven库,先操作spring-boot-hello-service-starter-autoconfigure

最后操作customer-starter这样自定义的starter就好了。

现在新建一个SpringWeb项目测试,在这个项目中引入刚刚自定义的starter

<dependencies>
  <dependency>
    <groupId>top.ctong</groupId>
    <artifactId>customer-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
  </dependency>
</dependencies>

在测试项目的配置文件中添加需要的配置

clover.hello.prefix=CLOVER
clover.hello.suffix=CLOVER NP~~

编写一个Controller测试

@RestController
public class HelloController {

  public final HelloService helloService;

  @Autowired
  public HelloController(HelloService helloService) {
    this.helloService = helloService;
  }

  @RequestMapping("/hello")
  public String sayHello() {
    return helloService.sayHello("Clover");
  }
}

截屏2021-07-18 18.36.25

SpringBoot 原理

Spring原理、SpringMVC原理、自动配置原理、SpringBoot原理

SpringApplication创建初始化流程

  • 创建SpringApplication

    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
      return new SpringApplication(primarySources).run(args);
    }
    

    org.springframework.boot.SpringApplication#run(java.lang.Class<?>[], java.lang.String[])

    • 保存当前应用程序信息

      public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
      }
      

      org.springframework.boot.SpringApplication#SpringApplication(org.springframework.core.io.ResourceLoader, java.lang.Class<?>...)

    • 创建初始启动器:Bootstrapper.class
      去所有的spring.factories文件中找org.springframework.boot.Bootstrapper

      public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
      }
      
    • 在所有spring.factories中找所有的ApplicationContextInitializer

    • 在所有spring.factories中找所有的ApplicationListener

  • 运行SpringApplication

    org.springframework.boot.SpringApplication#run(java.lang.String...)

    • 监控整个应用启动/停止:StopWatch

      • 记录应用启动时间
    • 创建引导上下文

      org.springframework.boot.SpringApplication#createBootstrapContext

      • 创建默认上下文

        DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
        
      • 获取之前保存的所有org.springframework.boot.SpringApplication#bootstrapRegistryInitializers,然后遍历它,调用每一项中的initialize方法,因为它是bootstrapRegistryInitializers,是一个org.springframework.boot.BootstrapRegistryInitializer接口

        @FunctionalInterface
        public interface BootstrapRegistryInitializer {
        
          void initialize(BootstrapRegistry registry);
        
        }
        

        initialize方法传入上一步创建的上下文DefaultBootstrapContext来完成对引导启动器上下文环境设置

        this.bootstrapRegistryInitializers.forEach((initializer) -> initializer.initialize(bootstrapContext));
        
    • 让当前应用进入headlless模式

    • 获取并保存运行时监听器

      SpringApplicationRunListeners listeners = getRunListeners(args);
      
    • 调用所有监听器的starting方法
      通知所有监听器,当前项目正在启动

      org.springframework.boot.SpringApplicationRunListener#starting(org.springframework.boot.ConfigurableBootstrapContext)

    • 保存命令行参数

      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      
    • 准备环境

      ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
      

      org.springframework.boot.SpringApplication#prepareEnvironment

      • 准备一个基础环境,如果不存在则创建

        ConfigurableEnvironment environment = getOrCreateEnvironment();
        
      • 配置环境信息

        configureEnvironment(environment, applicationArguments.getSourceArgs());
        
        • 配置转换器

          if (this.addConversionService) {
           environment.setConversionService(new ApplicationConversionService());
          }
          
        • 加载外部配置信息,读取所有配置源属性值

          configurePropertySources(environment, args);
          
      • 绑定环境信息

        ConfigurationPropertySources.attach(environment);
        
      • 调用所有监听器environmentPrepared方法

        此时所有环境已经准备完成

        listeners.environmentPrepared(bootstrapContext, environment);
        
      • 判断如果没有自定义环境信息就使用默认的环境

        if (!this.isCustomEnvironment) {
          environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                                                                                                 deduceEnvironmentClass());
        }
        
      • 最后再把环境信息合并

        ConfigurationPropertySources.attach(environment);
        
    • 创建IOC容器

      context = createApplicationContext();
      

      根据项目类型进行创建

      ApplicationContextFactory DEFAULT = (webApplicationType) -> {
        try {
          switch (webApplicationType) {
            case SERVLET:
              return new AnnotationConfigServletWebServerApplicationContext();
            case REACTIVE:
              return new AnnotationConfigReactiveWebServerApplicationContext();
            default:
              return new AnnotationConfigApplicationContext();
          }
        }
        catch (Exception ex) {
          throw new IllegalStateException("Unable create a default ApplicationContext instance, "
                                          + "you may need a custom ApplicationContextFactory", ex);
        }
      };
      
      ConfigurableApplicationContext create(WebApplicationType webApplicationType);
      
    • 准备IOC容器上下文信息

      prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
      

      org.springframework.boot.SpringApplication#prepareContext

      • 保存前面准备好的基础环境到容器

        context.setEnvironment(environment);
        
      • 在这一步之前,IOC默认配置都准备得差不多了,这一步的目的是使用ApplicationContextInitializer来对进行扩展。

        protected void applyInitializers(ConfigurableApplicationContext context) {
          for (ApplicationContextInitializer initializer : getInitializers()) {
            Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
                                                                            ApplicationContextInitializer.class);
            Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
            initializer.initialize(context);
          }
        }
        
      • 在容器准备完后再调用所有SpringApplicationRunListener中的contextPrepared(context)方法

      • 获取Bean工厂,这个工厂是在容器new的过程中被初始化的,具体在org.springframework.context.support.GenericApplicationContext#GenericApplicationContext()

        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        
      • 将命令行参数组册到容器

        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        
      • org.springframework.boot.SpringApplication#prepareContext这个方法准备完成之后再调用所有的监听器中的contextLoaded(context)方法。

    • 刷新容器

      refreshContext(context);
      
      • 创建容器中的所有组件
    • 记录容器启动完成花费的时间

      stopWatch.stop();
      
    • 容器启动完成之后调用所有监听器的started(context)方法

      listeners.started(context);
      
    • 调用所有runners

      callRunners(context, applicationArguments);
      
      • 获取容器中的ApplicationRunner

        runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
        
      • 获取容器中的CommandLineRunner

        runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
        
      • 合并所有runner并且安装@Order进行排序

        AnnotationAwareOrderComparator.sort(runners);
        
      • 遍历所有的runner调用run方法

        for (Object runner : new LinkedHashSet<>(runners)) {
          if (runner instanceof ApplicationRunner) {
            callRunner((ApplicationRunner) runner, args);
          }
          if (runner instanceof CommandLineRunner) {
            callRunner((CommandLineRunner) runner, args);
          }
        }
        
        private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
          try {
            (runner).run(args);
          }
          catch (Exception ex) {
            throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
          }
        }
        
    • 如果以上有异常,会调用所有监听器的listeners.failed(context, exception);方法

      try {...} catch (Throwable ex) {
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
      }
      
    • 没有任何异常发生时,会调用所有监听器的running方法

      listeners.running(context);
      
    • 如果listeners.running(context);中有异常,会继续调用

      handleRunFailure(context, ex, null);
      
    • 到此,SpringBoot启动成功

自定义监听组件

ApplicationContextInitializer

这需要到spring.factories中注册

@Slf4j
public class MyApplicationContextInitializer
        implements Serializable, ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        log.info("容器初始化...");
    }

}
org.springframework.context.ApplicationContextInitializer=\
  com.example.springapplication.listener.MyApplicationContextInitializer

ApplicationListener

这需要到spring.factories中注册

@Slf4j
public class MyApplicationListener implements Serializable, ApplicationListener<ApplicationEvent> {

    private static final long serialVersionUID = 6443715384736562018L;

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        log.info("MyApplicationListener: 绑定事件");
    }

}
org.springframework.context.ApplicationListener=\
  com.example.springapplication.listener.MyApplicationListener

SpringApplicationRunListener

这需要到spring.factories中注册

@Slf4j
public class MySpringApplicationRunListener implements Serializable, SpringApplicationRunListener {

  private static final long serialVersionUID = -8494070955501588597L;
  
  private SpringApplication app;

  public MySpringApplicationRunListener(SpringApplication app, String[] args) {
		this.app = app;
  }


  @Override
  public void starting(ConfigurableBootstrapContext bootstrapContext) {
    log.info("MySpringApplicationRunListener starting方法执行");
    SpringApplicationRunListener.super.starting(bootstrapContext);
  }

  @Override
  public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
                                  ConfigurableEnvironment environment) {
    log.info("MySpringApplicationRunListener environmentPrepared方法执行");
    SpringApplicationRunListener.super.environmentPrepared(bootstrapContext, environment);
  }
}
org.springframework.boot.SpringApplicationRunListener=\
  com.example.springapplication.listener.MySpringApplicationRunListener

ApplicationRunner

在底层ApplicationRunner是从容器中获取,所以需要注册到容器

@Component
@Slf4j
public class MyApplicationRunner {

    @Bean
    public ApplicationRunner applicationRunner() {
      return args -> log.info("applicationRunner runner...执行");
    }

}

CommandLineRunner

@Component
@Slf4j
public class MyApplicationRunner {

		@Bean
    public CommandLineRunner commandLineRunner() {
        return args -> log.info("commandLineRunner run...执行");
    }

}

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×