Spring Boot Profiles 激活方式

spring boot:

Profile 的概念其实很早在 Spring Framework 就有了,在 Spring Framework 3.1 版本引入了注解 @Profile和 Environment 环境配置的抽象,只是在 Spring Boot 框架里再进一步将 Profiles 功能进行扩展,使它也成为了 Spring Boot 特性之一,为此单独在 官方文档 25. Profiles 一节里介绍,文档里把 Spring Boot Profiles 也叫做 Spring Profiles。

那么什么又是 Spring Profiles,为什么需要它呢?我们先来看一个熟悉的场景:我们平常项目开发,经常需要根据不同的环境进行配置的修改,比如在本地开发会加载本机的配置和开发环境数据库,在测试服务器上部署时就需要加载测试环境配置和数据库,同样地,当项目发布生产环境时就需要设置为生产环境配置和数据库。这样一来,不同的环境部署都需要额外的处理来调整环境的配置,维护起来十分繁琐,还容易出错。

为了解决不同环境需要的配置切换问题,Spring Profiles 提供了一种方式允许我们指定在特定环境下只加载对应的程序配置,每一种环境配置对应一个 Profile,只有当前 Profile 处于激活状态时,才会将该 Profile 所对应的配置和 Bean 加载到 Spring 程序中。

Spring Profiles 就是针对应用程序,不同环境需要不同配置加载的一种解决方案。

当然 Spring 允许多个 Profile 处于激活状态,比如将应用配置进行细分成数据库配置,消息中间件配置,缓存配置等,都为各自在不同环境定义不同的 Profile 名称,在需要激活环境对应配置时,指定多个 Profile。

Spring Profiles 实战

在 Spring 程序中有两种方式使用 Profiles:XML 配置和注解 @Profile

XML 配置定义 Profile

虽然现在 XML 配置方式使用越来越少,还是简单介绍下,通常我们在 XML 文件定义的 Bean 时都有根元素 <beans>,在 beans 元素上多了一个属性 profile 可以指定环境,比如说把开发环境的 profile定义为 dev,生产环境的 profile 为:prod。

需要注意的是:必须要使用 Spring XML Beans Schema 版本为 4.0 以上才支持 profile 属性。在 XML 文件定义之后我们只需要激活指定的 Profile 名称就可以加载对应的 Bean 对象了,在 Spring 程序中激活的方式主要两种:

  • Java API 方式,获取当前 Spring 容器的环境 Bean,设置 activeProfiles 属性,然后启动容器

  • 采用启动参数方式指定,固定格式:-Dspring.profiles.active=dev

注解 @Profiles 定义Profile

使用注解定义 Profile 也比较简单,引入一个新的注解 @Profiles,通常 @Profiles 配合 @Component或者 @Configuration 使用,如下示例:

激活 Profile 的方式都是一样的,只要指定 Profile 被激活,其对应的 Bean 才会加载。在 Spring 程序中 Profile 默认为 default,当前我们可以通过 spring.profiles.default 配置方式或者 org.springframework.core.env.AbstractEnvironment#setDefaultProfiles API 方式修改。

Spring Boot Profile 实战

好了,现在我们再来看下在 Spring Boot 程序中如何使用 Profile。通常一个 Spring Boot 程序的配置文件为 yml 或者 properties 格式,由于 yml 格式文件的结构简洁已读,备受官方推崇,我们可以看下如何在 application.yml 定义 Profile 和对应的配置。

与yml格式文件不同,正对不同的 Profile,无法在一个 properties 文件设置,官方采用命名形式为 applications-${profile}.properties 格式来达成一样的效果。为了看到指定 Profile 激活后的效果,我们可以通过下方的一个例子实践下,通过激活不同 Profile 启动程序,来请求 /enviroment 接口来获取当前的环境配置变量。

这里我们介绍如何在配置文件中激活 Profile 的方式:在 application.yml 顶部添加如下配置,表明当前所激活的 Profile 为 prod,当然也可以前文介绍的启动参数方式激活:

然后启动程序,curl 方式访问 http://localhost:9000/enviroment 可以得到如下输出结果:

同样如果上述的 active 属性值指定为 dev,将输出内容: current app enviroment is prod

Spring Boot API 方式激活 Profile

在 Spring Boot 程序除了上述的方法来激活 Profile 外,还可以使用 Spring Boot API 方式激活:

  • SpringApplication.setAdditionalProfiles(…)

  • SpringApplicationBuilder.profiles(...)

但需要注意的是使用 Spring Boot API 的话需要在程序启动前设置,也就是 SpringApplication.run(...)方法执行前,否则没有效果。 采用 Spring Boot API 方式添加的Profile 是属于额外激活的 Profile,也就是说覆盖掉外部传入的 spring.profiles.activie 指定的 Profile。

总结

在Spring Boot 程序中,我们通常定义不同 Profiles 的配置文件,如 application-{profile}.properties,在默认配置文件 application.properties 中设置 spring.profiles.active=dev ,用于平常开发使用,当需要打包上传服务器时,通过启动参数方式 jar -Dspring.profiles.active=prod xxx.jar 指定对应环境的 Profile 启动程序来加载对应环境的配置,到这里我们学习如何通过 Spring Boot Profiles 特性来应对程序中不同环境配置的切换,希望对工作中的小伙伴有所帮助,也欢迎小伙伴留言分享应对项目环境配置区分加载的实践心得

 

springmvc

 

项目中一直应用Maven的profile特性解决不同环境的部署问题。最近在尝试解决本地调试环境的时候碰到一些问题,顺便仔细研究了一下。因为项目仍然在用普通SpringMVC架构,没有切换到Spring Boot,所以例子以SpringMVC为基础。

这里就不介绍Profile的基础知识了,不了解的请找相关资料查一下。

1    Profile的基础使用

我们常见的两种使用Profile的方法:占位符替换和文件复制。

1.1 Profile定义

在项目的pom.xml中定义不同的profile,以数据库主机地址为例。

<profiles>

  <profile>

    <id>dev</id>

    <properties>

      <active.profile>dev</active.profile>

      <database.host>localhost</database.host>

    </properties>

  </profile>

  <profile>

    <id>test</id>

    <properties>

      <active.profile>test</active.profile>

      <database.host>test.codestory.tech</database.host>

    </properties>

  </profile>

  <profile>

    <id>prod</id>

    <properties>

      <active.profile>prod</active.profile>

      <database.host>prod.codestory.tech</database.host>

    </properties>

  </profile>

</profiles>

 

1.2 替换占位符方法

为了简化,将本位涉及的所有参数保存到 src/main/resources/config下的props.properties 文件中,格式为

database.pool.host=${database.host}

在pom.xml中定义 resources 插件,定制资源复制的动作。

<build>

  <resources>

    <resource>

      <directory>src/main/resources</directory>

      <filtering>true</filtering><!-- 替换占位符 -->

    </resource>

  </resources>

  <plugins>

    <plugin>

      <groupId>org.apache.maven.plugins</groupId>

      <artifactId>maven-resources-plugin</artifactId>

      <version>3.0.2</version>

      <configuration>

        <encoding>UTF-8</encoding>

        <overwrite>true</overwrite><!-- 目标文件存在时覆盖 -->

      </configuration>

    </plugin>

  </plugins>

</build>

执行 maven 命令,指定 profile 复制资源,复制的资源在目录 target/classes 下。分别用三个不同的profile执行mvn 命令后结果如下:

mvn clean resources:resources -P dev

database.pool.host=localhost

mvn clean resources:resources -P test

database.pool.host=test.codestory.tech

mvn clean resources:resources -P prod

database.pool.host=prod.codestory.tech

 

1.3 复制文件方法

除了使用properties替换占位符的方法,还可以分别为每个profile编写文件,打包时根据选择的profile进行复制。

创建各个profile需要的配置文件,在src/main/resources 中创建目录 profiles ,并在其中创建三个子目录:dev/test/prod,每个子目录中创建一个props.properties文件,内容分别为

src/main/resources/profiles/dev/props.properties

database.pool.host=localhost

src/main/resources/profiles/test/props.properties

database.pool.host=test.codestory.tech

src/main/resources/profiles/prod/props.properties

database.pool.host=prod.codestory.tech

为了测试resources-plugin的参数 overwrite ,我们将 src/main/resources/config/props.properties 内容增加一行,变为

database.pool.host=${database.host}

database.pool.port=3306

在pom.xml中修改resources部分配置

<resources>

  <resource>

    <directory>src/main/resources</directory>

    <excludes>

      <exclude>profiles/**</exclude>

    </excludes>

    <filtering>true</filtering><!-- 替换占位符 -->

  </resource>

  <resource>

    <directory>src/main/resources/profiles/${active.profile}</directory>

    <targetPath>config</targetPath>

    <filtering>false</filtering><!-- 不替换占位符,直接复制 -->

  </resource>

</resources>

同样执行maven resources命令后查看文件内容,

mvn clean resources:resources -P dev

database.pool.host=localhost

注意属性文件中没有 database.pool.port=3306 这一行,说明是复制文件的结果,而不是直接替换占位符。

2    同时使用多个profile

前面的例子足够简单,也能解决大部分场景下打包的问题。扩展一下场景,看看问题如何解决?

2.1 本地用test环境调试

为了场景需要,假设props.properties文件中还有一个参数,用于记录附件的保存路径(为了场景假设的,使用分布式文件服务器或webdav等技术的同学请忽视)。

database.pool.host=${database.host}

filesystem.path.root=${path.root}

现在测试同学在测试环境发现了BUG,开发需要访问test环境数据库进行联调,但附件保存路径不同,本地不能直接使用 -P test。使用Tomcat远程调试的同学也请绕道一下。另外还有一个简单的办法,修改一下pom.xml中的profile[test]中path.root参数即可解决。不过为了研究profile,也不用这个太简单的方案。

2.2 多个profile 替换占位符的方法

解决的思路是保持原有的profile配置信息不变,额外选中一个本地调试用的profile,替换其中少量参数。

pom.xml中profiles内容修改为

<profiles>

  <profile>

    <id>local</id>

    <properties>

      <active.profile>local</active.profile>

      <path.root>d:/develop/attachments</path.root>

    </properties>

  </profile>

  <profile>

    <id>dev</id>

    <properties>

      <active.profile>dev</active.profile>

      <database.host>localhost</database.host>

      <path.root>d:/develop/attachments</path.root>

    </properties>

  </profile>

  <profile>

    <id>test</id>

    <properties>

      <active.profile>test</active.profile>

      <database.host>test.codestory.tech</database.host>

      <path.root>/app/attachments</path.root>

    </properties>

  </profile>

  <profile>

    <id>prod</id>

    <properties>

      <active.profile>prod</active.profile>

      <database.host>prod.codestory.tech</database.host>

      <path.root>/app/attachments</path.root>

    </properties>

  </profile>

</profiles>

使用多个profile,在-P参数后,只需要用逗号分隔即可。我的目的是用local中的参数替换test中同名参数,所以将 local放在后面。(需要在pom.xml中注释掉<directory>src/main/resources/profiles/${active.profile}</directory>这个resource定义)

mvn clean resources:resources -P test,local

database.pool.host=test.codestory.tech

filesystem.path.root=/app/attachments

发现文件内容并没有按照我预期的目标替换,而是仍然用了test的参数。在网上搜索,在百度知道一个回答中找到了答案 https://zhidao.baidu.com/question/139071460381210925.html ,【它是根据profile定义的先后顺序来进行覆盖取值的,然后后面定义的会覆盖前面定义的。】

因此,修改 pom.xml中profiles的顺序,将local放到最后,重新执行命令

mvn clean resources:resources -P test,local

database.pool.host=test.codestory.tech

filesystem.path.root= d:/develop/attachments

 

2.3 多个profile复制文件

再来试试复制文件的方法是否继续有效。为了测试方便,在profiles/{active.profile}的目录下,分别放置了一个不同的属性文件,文件名含profile名,分别为env-dev.properties/env-test.properties /env-prod.properties。

首先,只用一个profile测试

mvn clean resources:resources -P test

在target/classes/config 目录中可以看到两个文件 env-test.properties和props.properties,说明复制文件成功;查看文件内容,可以发现都是从 src/main/resources/profiles/test 目录复制而来。

测试两个profile,再检查目录 target/classes/config,发现只有一个文件 props.properties,并且内容是 src/main/resource/config/props.properties文件替换占位符的结果。

mvn clean resources:resources -P test,local

database.pool.host=test.codestory.tech

filesystem.path.root=d:/develop/attachments

为了测试原因,在 src/main/resource/config/props.properties 中增加一个参数activeProfiles,文件内容为:

database.pool.host=${database.host}

filesystem.path.root=${path.root}

active.profiles=${active.profile}

mvn clean resources:resources -P test,local

database.pool.host=test.codestory.tech

filesystem.path.root=d:/develop/attachments

active.profiles=local

原因在于:根据优先级,参数active.profile只保留了最后一个 local,所以无法实现拷贝 test 目录下文件的效果。

2.4 修改profile复制文件方法

在maven的pom规范中,在每个profile中还可以定义build参数,因此将pom.xml中profiles部分内容修改为

<profiles>

  <profile>

    <id>dev</id>

    <properties>

      <active.profile>dev</active.profile>

      <database.host>localhost</database.host>

      <path.root>d:/develop/attachments</path.root>

    </properties>

    <build>

      <resources>

        <resource>

          <directory>src/main/resources/profiles/dev</directory>

          <targetPath>config</targetPath>

          <filtering>false</filtering>

        </resource>

      </resources>

    </build>

  </profile>

  <profile>

    <id>test</id>

    <properties>

      <active.profile>test</active.profile>

      <database.host>test.codestory.tech</database.host>

      <path.root>/app/attachments</path.root>

    </properties>

    <build>

      <resources>

        <resource>

          <directory>src/main/resources/profiles/test</directory>

          <targetPath>config</targetPath>

          <filtering>false</filtering>

        </resource>

      </resources>

    </build>

  </profile>

  <profile>

    <id>prod</id>

    <properties>

      <active.profile>prod</active.profile>

      <database.host>prod.codestory.tech</database.host>

      <path.root>/app/attachments</path.root>

    </properties>

    <build>

      <resources>

        <resource>

          <directory>src/main/resources/profiles/prod</directory>

          <targetPath>config</targetPath>

          <filtering>false</filtering>

        </resource>

      </resources>

    </build>

  </profile>

  <profile>

    <id>local</id>

    <properties>

      <active.profile>local</active.profile>

      <path.root>d:/develop/attachments</path.root>

    </properties>

  </profile>

</profiles>

可以看到,在每个profile中增加了文件复制的内容。同之前配置的区别在于:不再使用变量 ${active.profile},而是直接写profile的名称。删除之前定义的<directory>src/main/resources/profiles/${active.profile}</directory>,再次测试

mvn clean resources:resources -P test,local

在target/classes/config 目录中可以看到两个文件 env-test.properties和props.properties,说明复制文件成功。

当然这时候想达到本节开始的场景:本地使用test数据库调试,需要拆分props.properties为两个文件,分别处理了:数据库信息放一个文件(使用复制文件的方法),文件目录放另一个文件(使用替换占位符的方法)。

 

3    尝试在项目配置文件中记录所使用的Profiles

前面的例子中,使用active.profiles=${active.profile}记录的值,只有最后一个profile的id。如果想记录所有使用到的profile,希望配置文件中的值是active.profiles=test,local。该怎么做呢?

经过测试,发现maven有一个内置参数是 activeProfiles。将原始配置文件修改为 active.profiles=${activeProfiles}

mvn clean resources:resources -P test,local

active.profiles=[Profile {id: test, source: pom}, Profile {id: local, source: pom}]

在网上搜索了很久,没发现用什么办法能够处理${activeProfiles}的输出值。不过文本也足够简单,可以在项目中读出这个字符串后进行后续处理,比如处理为: active.profiles=test,local

4    在Maven的settings.xml中定义profile

除了项目pom.xml中定义profile,还可以在maven/conf/settings.xml中定义。为了测试profile的优先级,定义了两个profile,并且新加了一个属性active.profile.label,并且将local和test的顺序互换。

<profiles>

  <profile>

    <id>local</id>

    <properties>

      <active.profile>local</active.profile>

      <active.profile.label>settings profile local</active.profile.label>

      <filesystem.path.root>d:/develop/attachments</filesystem.path.root>

    </properties>

  </profile>

  <profile>

    <id>test</id>

    <properties>

      <active.profile>test</active.profile>

      <active.profile.label>settings profile test</active.profile.label>

    </properties>

  </profile>

</profiles>

创建一个profiles.txt文件用于输出,原始内容(为了区别输出内容,增加了#字符分隔行)

###############################################

active.profiles=${activeProfiles}

###############################################

active.profile.label=${active.profile.label}

###############################################

使用命令

mvn clean resources:resources -P test,local

###############################################

active.profiles=[Profile {id: test, source: pom}, Profile {id: local, source: pom}, Profile {id: local, source: settings.xml}, Profile {id: test, source: settings.xml}]

###############################################

active.profile.label=settings profile test

###############################################

由此可见,当同时在pom.xml和settins.xml中定义了相同id的profile,其加载顺序是先依次加载 pom.xml中的Profiles,再加载settings.xml中的profiles。当定义了相同名称的属性时,很可能会导致意外的结果。

 

原文链接:https://www.cnblogs.com/one12138/p/11273031.html

https://www.cnblogs.com/codestory/p/11271016.html

posted @ 2019-07-31 12:40  川流不息&  阅读(648)  评论(0编辑  收藏  举报