6、高级特性
6.1 Profile功能
为了方便多环境适配,比如在测试环境和生产环境中的切换,Springboot简化了profile功能。
1、application-profile功能
在SpringBoot中,默认的配置文件是以application命名的properties或者yaml文件,针对不同的开发环境的相关配置,可以使用 application-***.yaml/yml/properties 的命名方式。
- 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,输出效果也如下所示:
- 默认配置与环境配置同时生效,但修改配置文件的任意值时,环境配置优先
- 激活指定的环境:
- 配置文件激活
- 命令行激活: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文件)
1、外部配置源
常用:Java属性文件、YAML文件、环境变量、命令行参数;
优先级:命令行 > 环境变量 > 配置文件,下图优先级中,数字越大优先级越高
2、配置文件查找位置与顺序
(1) classpath(resources文件夹) 根路径
(2) classpath 根路径下config目录
(3) jar包当前目录
(4) jar包当前目录的config目录
(5) /config子目录的直接子目录
注意:配置文件的优先级的大小,从下往上,越往下优先级越高,会覆盖同名配置项。例如:类路径下的config文件夹中的application.yml文件优先级比类路径下的要高,即会使用config文件夹下的配置文件中的内容。
3、配置文件加载顺序
- 当前jar包内部的application.properties和application.yml
- 当前jar包内部的application-{profile}.properties 和 application-{profile}.yml
- 引用的外部jar包的application.properties和application.yml
- 引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml
加载顺序从上往下加载,因此最后在外部的配置文件会进行覆盖。
4、总结
指定环境优先,外部优先,后面的可以覆盖前面的同名配置项的值。
6.3 自定义starter(重要)
1、starter启动原理
- pom文件中引入某个starter,进入starter后可以发现,是引入其他的一些依赖,最重要的是引入相关的 autoconfigurer 包
- autoconfigure包中配置使用 META-INF/spring.factories 中 EnableAutoConfiguration 的值,使得项目启动加载指定的自动配置类
注意是autoconfigure包下的
- 编写自动配置类 xxxAutoConfiguration -> xxxxProperties
- @Configuration
- @Conditional
- @EnableConfigurationProperties
- @Bean
- ......
引入starter --- xxxAutoConfiguration --- 容器中放入组件 ---- 绑定xxxProperties ---- 配置项
2、自定义starter
boot-09-customer-starter(启动器)
boot-09-customer-starter-autoconfigure(自动配置包)
在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仓库引入
(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
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
/**
* 创建一个新的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(启动引导器、容器初始化器、应用监听器三个)
进入SpringApplication的构造器方法:
- 保存启动类的信息(PrimarySources)
- 这里就能发现primarySources就是指的SpringBoot的启动类(主类)
- 判定当前应用的类型,通过ClassUtils进行判断(是响应式编程还是原生Servlet)。这里当前是Servlet
- 接下来是对三个模块的初始化操作:
- bootstrappers:初始启动引导器(List):去spring.factories文件中找 org.springframework.boot.Bootstrapper
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
- 找 ApplicationContextInitializer(容器初始化器);去 spring.factories 找ApplicationContextInitializer
- List<ApplicationContextInitializer<?>> initializers
- 找ApplicationListener 应用监听器。去spring.factories 找 ApplicationListener
- List<ApplicationListener<?>> listeners
2、运行SpringApplication
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)
- 进入public ConfigurableApplicationContext run(String... args) {
- 记录应用的启动时间
long startTime = System.nanoTime();
- 创建引导上下文(Context环境)createBootstrapContext()
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
- 获取到之前所有的引导器 bootstrappers ,并遍历挨个执行 intitialize() 来完成对引导启动器上下文环境设置
- 让当前应用进入headless模式。java.awt.headless
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进行事件感知】
- 通过getRunListeners(args)方法中的getSpringFactoriesInstances方法,去spring.factories 找 SpringApplicationRunListener
- 接着往下执行,调用listeners.starting(),遍历 SpringApplicationRunListener 调用 starting 方法;
listeners.starting(bootstrapContext, this.mainApplicationClass);
- 相当于通知所有正在监听系统启动过程的进程,springboot应用正在启动 starting。
- 保存命令行参数;ApplicationArguments
- 下一步,准备环境,调用 prepareEnvironment()方法;
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
- 返回或者创建基础环境信息对象。ApplicationServletEnvironment(实现了StandardServletEnvironment接口)
ConfigurableEnvironment environment = getOrCreateEnvironment();
- 配置环境信息对象。
configureEnvironment(environment, applicationArguments.getSourceArgs());//传入了环境信息、命令行参数信息
- 读取所有的配置源的配置属性值。
- 绑定环境信息
ConfigurationPropertySources.attach(environment);
- 监听器listeners调用 listener.environmentPrepared();通知所有的监听器当前环境准备完成
listeners.environmentPrepared(bootstrapContext, environment);
- 接下来,就是一系列环境信息的初始化和绑定,基础环境准备部分结束,最后返回环境对象
- 基础环境创建完成后,接着是忽略一些bean信息,以及打印banner
- (重要)创建IOC容器 createApplicationContext()
- 根据项目类型(Servlet)创建容器
- 所以当前会创建容器: AnnotationConfigServletWebServerApplicationContext
- 准备ApplicationContext IOC容器的基本信息;调用prepareContext()方法
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
- 进入prepareContext():准备IOC容器的方法
注意:每当容器准备就绪、组件添加完成、加载完毕后,都要通知监听器相应阶段已经完成
- 保存环境信息:context.setEnvironment(environment);
- IOC容器的后置处理流程:postProcessApplicationContext(context);
- 应用初始化器:applyInitializers(context);
- 遍历所有的 ApplicationContextInitializer 。调用 initialize.。来对ioc容器进行初始化扩展功能
- 遍历所有的 listener ,调用 contextPrepared()方法进行遍历。EventPublishRunListenr来通知所有的监听器:容器上下文已经准备完毕(容器中并没有实例)
- 将启动所需的组件添加到容器中:比如banner
- 所有的监听器又调用contextLoaded。通知所有的监听器contextLoaded,也就是context(IOC容器)已经加载完成了
- 刷新IOC容器:refreshContext(context);
run方法当前已经执行到的位置如下:
- 一直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方法。
- 获取容器中的 ApplicationRunner:启动获取命令行参数
- 获取容器中的 CommandLineRunner:启动获取应用启动的时候参数
- 合并所有runner并且按照@Order进行排序
- 遍历所有的runner。调用 run 方法
- 有一个异常监听器,如果以上有异常,就会调用Listener 的 failed
- 如果没有异常的话,就又会通知所有监听器,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