SpringBoot Profiles特性

简介: SpringBoot Profiles特性

一、外部化配置


 配置分为编译时和运行时,而Spring采用后者,在工作中有时也会两者一起使用。

 何为“外部化配置”官方没有正面解释。通常,对于可扩展性应用,尤其是中间件,它们的功能性组件是可配置化的,如线程池配置及数据库连接信息等。

 假设设置Spring应用的Profile为dev,通过 ConfigurableEnvironment#setDefaultProfiles 方法实现,这种通过代码的方式配置,配置数据来源于应用内部实现的称为“内部化配置”。

 SpringBoot内置了17种外部化配置,并规定了其调用顺序。实际不止17种,也并不是必须按官方规定的顺序。

官方说明:

4.2. Externalized Configuration

Spring Boot lets you externalize your configuration so that you can work with the same application code in different environments. You can use properties files, YAML files, environment variables, and command-line arguments to externalize configuration. Property values can be injected directly into your beans by using the @Value annotation, accessed through Spring’s Environment abstraction, or be bound to structured objects through @ConfigurationProperties.

Spring Boot uses a very particular PropertySource order that is designed to allow sensible overriding of values. Properties are considered in the following order:

Devtools global settings properties in the $HOME/.config/spring-boot folder when devtools is active.
@TestPropertySource annotations on your tests.
properties attribute on your tests. Available on @SpringBootTest and the test annotations for testing a particular slice of your application.
Command line arguments.
Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property).
ServletConfig init parameters.
ServletContext init parameters.
JNDI attributes from java:comp/env.
Java System properties (System.getProperties()).
OS environment variables.
A RandomValuePropertySource that has properties only in random.*.
Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants).
Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants).
Application properties outside of your packaged jar (application.properties and YAML variants).
Application properties packaged inside your jar (application.properties and YAML variants).
@PropertySource annotations on your @Configuration classes.
Default properties (specified by setting SpringApplication.setDefaultProperties).
配置的引用方式
1)XML 文件

根据spring规范,元信息存放在META-INF目录下。

示例: 在resources目录下创建META-INF/spring/context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
 
<bean id="xmlPerson" class="com.example.profiledemo.property.XmlPerson">
    <property name="name" value="xml name" />
    <property name="age" value="10" />
</bean>
</beans>

定义JavaBean

/*
 * @auth yuesf
 * @data 2019/11/23
 */
public class XmlPerson {
    private String name;
    private String age;
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getAge() {
        return age;
    }
 
    public void setAge(String age) {
        this.age = age;
    }
}

使用xml配置属性

/*
 * @auth yuesf
 * @data 2019/11/23
 */
@RestController
@ImportResource(locations = { "META-INF/spring/context.xml" })
public class ContextController {
 
    @Autowired
    private XmlPerson xmlPerson;
 
    @GetMapping("/xml")
    public XmlPerson xml() {
        return xmlPerson;
    }
}

启动服务运行结果如下

{
"name": "xml name",
"age": "10"
}
2)Annotation

官方提供两种方式 @Value、@ConfigurationProperties

(1)@Value

@Value是绑定application配置文件的属性变量

示例:

applicaton.properties文件配置

person.name=yuesf

使用Annotation配置属性

@Value("${person.name:defaultValue}")
private String name;

@Value在Spring中是强校验,使用时必须在配置中存在,否则会无法启动,示例中采用容错的方式,不存在使用默认值。

@Value的语义可以参考java.util.Properties#getProperty(java.lang.String, java.lang.String)方法, 如果变量存在,则取变量值,若不存在取默认值

(2)@ConfigurationProperties

官方说明:

4.2.8. Type-safe Configuration Properties

Using the @Value("${property}") annotation to inject configuration properties can sometimes be cumbersome, especially if you are working with multiple properties or your data is hierarchical in nature. Spring Boot provides an alternative method of working with properties that lets strongly typed beans govern and validate the configuration of your application.

使用@Value来表达多个属性时特别麻烦,官方说明使用与JavaBean绑定的方式联合使用,使用方式如下:

使用@ConfigurationProperties 需要两步完成使用

  1. 必须要定义一个类来与属性做绑定。

示例说明:

/*
 * @auth yuesf
 * @data 2019/11/22
 */
@ConfigurationProperties("person")
public class Person {
    private String name;
    private String age;
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getAge() {
        return age;
    }
 
    public void setAge(String age) {
        this.age = age;
    }
}
  1. 使用@EnableConfigurationProperties 激活Person配置

示例说明:

@SpringBootApplication
@EnableConfigurationProperties(Person.class)
public class ProfileDemoApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(ProfileDemoApplication.class, args);
    }
}
3)Java Code (硬编码)
(1) 实现EnvironmentAware

示例通过实现EnvironmentAware 接口来自定义server.port端口号为7070:

/*
 * @auth yuesf
 * @data 2019/11/26
 */
@Component
public class CustomizedEnvironment implements EnvironmentAware {
    @Override
    public void setEnvironment(Environment environment) {
        System.out.println("当前激活profile文件是:"+Arrays.asList(environment.getActiveProfiles()));
        if(environment instanceof ConfigurableEnvironment){
            ConfigurableEnvironment env = ConfigurableEnvironment.class.cast(environment);
            MutablePropertySources propertySources = env.getPropertySources();
            Map<String, Object> source = new HashMap<>();
            source.put("server.port","7070");
            PropertySource propertySource = new MapPropertySource("javacode",source);
            propertySources.addFirst(propertySource);
        }
    }
}

启动后验证端口号未发生变更,不是我们想要的效果

...
 The following profiles are active: dev
2019-11-26 18:04:26.850  INFO 54924 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
...
当前激活profile文件是:[dev]
...

通过actuator查看端口号已经变更

http://127.0.0.1:8080/actuator/env/server.port

"propertySources": 
[
    {
        "name":"server.ports"
    },
    {
        "name":"javacode",
        "property":{
            "value":"7070"
        }
    },
    {
    "name": "commandLineArgs"
    },
    ...
]

问题:

这里会遇到一个问题,请问为什么这里的7070端口号没有使用呢?

文中javacode 是我们代码中指定的名称。propertySources的取值逻辑是顺序读取,一但有值就会返回。而返回后又对propertySources做了addFirst操作,所以会造成相互覆盖。

源码地址: org.springframework.core.env.PropertySourcesPropertyResolver#getProperty(java.lang.String, java.lang.Class , boolean)

要想使用改后的属性,我们可以仿照源码使用下面这种自定义事件ApplicationListener 的方式。

(2)自定义事件ApplicationEnvironmentPreparedEvent
/*
 * @auth yuesf
 * @data 2019/11/23
 */
public class CustomizedSpringBootApplicationListener
        implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        ConfigurableEnvironment env = event.getEnvironment();
        MutablePropertySources propertySources = env.getPropertySources();
        Map<String, Object> source = new HashMap<>();
        source.put("server.port","6060");
        PropertySource propertySource = new MapPropertySource("customizedListener",source);
        propertySources.addFirst(propertySource);
    }
}

添加Spring SPI配置 META-INF/spring.factories

# Application Listeners
org.springframework.context.ApplicationListener=\
com.example.profiledemo.listener.CustomizedSpringBootApplicationListener

启动后验证结果,启动端口已经生效

...
The following profiles are active: dev
Tomcat initialized with port(s): 6060 (http)
...
当前激活profile文件是:[dev]
...

通过actuator查看端口号,发现7070为第一个,6060为第二个。

"propertySources": 
[
    {
        "name":"server.ports"
    },
    {
        "name":"javacode",
        "property":{
            "value":"7070"
        }
    },
    {
        "name":"customizedListener",
        "property":{
            "value":"6060"
        }
    },
    {
    "name": "commandLineArgs"
    },
    ...
]

根据结果猜测,这样结果虽然已经修改过来了,但由于后使用addFirst方法对顺序做了改动。把javacode 放在了第一位。

Profiles使用场景
1)XML文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd"
        profile="test">
    ...
</beans>

spring中对xml做了属性封装,使用profile方式来加载,示例中使用的是profile="test"

2)Properties文件

properties文件名按 application-{profile}.properties 规约来命名

3)Annotation使用

通过 @Profile 方式指定一个或多个 profile

4)命令行

通过--spring.profiles.active 命令行指定使用的profile,还可以使用 --spring.profiles.include引用多个profile

三、装配原理

通过上面说明并没有讲清楚他的装配原理是什么,那么我们通过源码了解下装配原理。

1.首先第一步是查看spring-boot源码#META-INF/spring.factories

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

PropertySourceLoader接口有两个实现

  • PropertiesPropertySourceLoader 解析properties和xml
public class PropertiesPropertySourceLoader implements PropertySourceLoader {
 
  private static final String XML_FILE_EXTENSION = ".xml";
 
  @Override
  public String[] getFileExtensions() {
      return new String[] { "properties", "xml" };
  }
    ...
}
  • YamlPropertySourceLoader 解析 yml和yaml
public class YamlPropertySourceLoader implements PropertySourceLoader {
 
  @Override
  public String[] getFileExtensions() {
      return new String[] { "yml", "yaml" };
  }
    ...
}

2.不管哪种解析,查下load方法是由谁来调用

本文使用idea查看代码,查看代码需要下载源码才可以查看。

本文中提到查看源码的方法调用统一使用idea自带的快捷键Alt+F7,或鼠标右键Find Usages

发现load方法是由ConfigFileApplicationListener.Loader#loadDocuments 方法调用。

再次查看ConfigFileApplicationListener 这个类是被谁调用,同样使用鼠标右键Find Usages ,发现会出来很多,那么我们会有选择性的查看。看哪一个呢?使劲找就能找到我们刚才看到的那个文件/META-INF/spring.factories 文件中有个ApplicationListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

查到这里就涉及到spring的事件,如果你不清楚Spring事件可以看下相关文档。

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
 
    /**
     * Handle an application event.
     * @param event the event to respond to
     */
    void onApplicationEvent(E event);
 
}

ApplicationListener 只有一个方法 onApplicationEvent 同样查看刚才我们定位到spring.factories文件查看 ConfigFileApplicationListener#onApplicationEvent 方法,

@Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationEnvironmentPreparedEvent) {
            onApplicationEnvironmentPreparedEvent(
                    (ApplicationEnvironmentPreparedEvent) event);
        }
        if (event instanceof ApplicationPreparedEvent) {
            onApplicationPreparedEvent(event);
        }
    }

这个方法非常简单,通过判断如果是 ApplicationEnvironmentPreparedEvent 类型时怎么做,意思是当应用环境准备时怎么做。第二个是如果是 ApplicationPreparedEvent类型怎么做,意思是应用准备时怎么做。

3.反过来在看下我们Java Code的方式 使用自定义事件时会生效的原因。

本次整个分析到此结束

相关文章
|
12月前
|
Java Spring
Spring Boot 启动报错解决:No active profile set, falling back to default profiles: default
Spring Boot 启动报错解决:No active profile set, falling back to default profiles: default
413 0
|
Java 测试技术 数据库
「Spring Boot 系列」05. Spring Boot Profiles(多环境配置)
「Spring Boot 系列」05. Spring Boot Profiles(多环境配置)
314 0
「Spring Boot 系列」05. Spring Boot Profiles(多环境配置)
|
Java Maven Spring
spring boot + maven使用profiles进行环境隔离
通过maven + springboot进行profiles的切换 spring boot热部署
4748 0
|
Java Maven Spring
How to set spring boot active profiles with maven profiles
In the previous post you could read about separate Spring Boot builds for a local development machine and public environments.
1466 0
|
16天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
93 1
|
2月前
|
前端开发 JavaScript Java
基于Java+Springboot+Vue开发的服装商城管理系统
基于Java+Springboot+Vue开发的服装商城管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的服装商城管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。
116 2
基于Java+Springboot+Vue开发的服装商城管理系统
|
2月前
|
前端开发 JavaScript Java
SpringBoot项目部署打包好的React、Vue项目刷新报错404
本文讨论了在SpringBoot项目中部署React或Vue打包好的前端项目时,刷新页面导致404错误的问题,并提供了两种解决方案:一是在SpringBoot启动类中配置错误页面重定向到index.html,二是将前端路由改为hash模式以避免刷新问题。
167 1
|
1天前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。
|
2月前
|
前端开发 JavaScript Java
基于Java+Springboot+Vue开发的大学竞赛报名管理系统
基于Java+Springboot+Vue开发的大学竞赛报名管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的大学竞赛报名管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。
165 3
基于Java+Springboot+Vue开发的大学竞赛报名管理系统
|
17天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用
【10月更文挑战第8天】本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,通过 Spring Initializr 创建并配置 Spring Boot 项目,实现后端 API 和安全配置。接着,使用 Ant Design Pro Vue 脚手架创建前端项目,配置动态路由和菜单,并创建相应的页面组件。最后,通过具体实践心得,分享了版本兼容性、安全性、性能调优等注意事项,帮助读者快速搭建高效且易维护的应用框架。
23 3