使用Profiles

Profile本身是Spring提供的功能,我们在使用条件装配中已经讲到了,Profile表示一个环境的概念,如开发、测试和生产这3个环境:

  • native
  • test
  • production

或者按git分支定义master、dev这些环境:

  • master
  • dev

在启动一个Spring应用程序的时候,可以传入一个或多个环境,例如:

  1. -Dspring.profiles.active=test,master

大多数情况下,使用一个环境就足够了。

Spring Boot对Profiles的支持在于,可以在application.yml中为每个环境进行配置。下面是一个示例配置:

  1. spring:
  2. application:
  3. name: ${APP_NAME:unnamed}
  4. datasource:
  5. url: jdbc:hsqldb:file:testdb
  6. username: sa
  7. password:
  8. dirver-class-name: org.hsqldb.jdbc.JDBCDriver
  9. hikari:
  10. auto-commit: false
  11. connection-timeout: 3000
  12. validation-timeout: 3000
  13. max-lifetime: 60000
  14. maximum-pool-size: 20
  15. minimum-idle: 1
  16. pebble:
  17. suffix:
  18. cache: false
  19. server:
  20. port: ${APP_PORT:8080}
  21. ---
  22. spring:
  23. profiles: test
  24. server:
  25. port: 8000
  26. ---
  27. spring:
  28. profiles: production
  29. server:
  30. port: 80
  31. pebble:
  32. cache: true

注意到分隔符---,最前面的配置是默认配置,不需要指定Profile,后面的每段配置都必须以spring.profiles: xxx开头,表示一个Profile。上述配置默认使用8080端口,但是在test环境下,使用8000端口,在production环境下,使用80端口,并且启用Pebble的缓存。

如果我们不指定任何Profile,直接启动应用程序,那么Profile实际上就是default,可以从Spring Boot启动日志看出:

  1. 2020-06-13 11:20:58.141 INFO 73265 --- [ restartedMain] com.itranswarp.learnjava.Application : Starting Application on ... with PID 73265 ...
  2. 2020-06-13 11:20:58.144 INFO 73265 --- [ restartedMain] com.itranswarp.learnjava.Application : No active profile set, falling back to default profiles: default

要以test环境启动,可输入如下命令:

  1. $ java -Dspring.profiles.active=test -jar springboot-profiles-1.0-SNAPSHOT.jar
  2. . ____ _ __ _ _
  3. /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
  4. ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
  5. \\/ ___)| |_)| | | | | || (_| | ) ) ) )
  6. ' |____| .__|_| |_|_| |_\__, | / / / /
  7. =========|_|==============|___/=/_/_/_/
  8. :: Spring Boot :: (v2.3.0.RELEASE)
  9. 2020-06-13 11:24:45.020 INFO 73987 --- [ main] com.itranswarp.learnjava.Application : Starting Application v1.0-SNAPSHOT on ... with PID 73987 ...
  10. 2020-06-13 11:24:45.022 INFO 73987 --- [ main] com.itranswarp.learnjava.Application : The following profiles are active: test
  11. ...
  12. 2020-06-13 11:24:47.533 INFO 73987 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8000 (http) with context path ''
  13. ...

从日志看到活动的Profile是test,Tomcat的监听端口是8000

通过Profile可以实现一套代码在不同环境启用不同的配置和功能。假设我们需要一个存储服务,在本地开发时,直接使用文件存储即可,但是,在测试和生产环境,需要存储到云端如S3上,如何通过Profile实现该功能?

首先,我们要定义存储接口StorageService

  1. public interface StorageService {
  2. // 根据URI打开InputStream:
  3. InputStream openInputStream(String uri) throws IOException;
  4. // 根据扩展名+InputStream保存并返回URI:
  5. String store(String extName, InputStream input) throws IOException;
  6. }

本地存储可通过LocalStorageService实现:

  1. @Component
  2. @Profile("default")
  3. public class LocalStorageService implements StorageService {
  4. @Value("${storage.local:/var/static}")
  5. String localStorageRootDir;
  6. final Logger logger = LoggerFactory.getLogger(getClass());
  7. private File localStorageRoot;
  8. @PostConstruct
  9. public void init() {
  10. logger.info("Intializing local storage with root dir: {}", this.localStorageRootDir);
  11. this.localStorageRoot = new File(this.localStorageRootDir);
  12. }
  13. @Override
  14. public InputStream openInputStream(String uri) throws IOException {
  15. File targetFile = new File(this.localStorageRoot, uri);
  16. return new BufferedInputStream(new FileInputStream(targetFile));
  17. }
  18. @Override
  19. public String store(String extName, InputStream input) throws IOException {
  20. String fileName = UUID.randomUUID().toString() + "." + extName;
  21. File targetFile = new File(this.localStorageRoot, fileName);
  22. try (OutputStream output = new BufferedOutputStream(new FileOutputStream(targetFile))) {
  23. input.transferTo(output);
  24. }
  25. return fileName;
  26. }
  27. }

而云端存储可通过CloudStorageService实现:

  1. @Component
  2. @Profile("!default")
  3. public class CloudStorageService implements StorageService {
  4. @Value("${storage.cloud.bucket:}")
  5. String bucket;
  6. @Value("${storage.cloud.access-key:}")
  7. String accessKey;
  8. @Value("${storage.cloud.access-secret:}")
  9. String accessSecret;
  10. final Logger logger = LoggerFactory.getLogger(getClass());
  11. @PostConstruct
  12. public void init() {
  13. // TODO:
  14. logger.info("Initializing cloud storage...");
  15. }
  16. @Override
  17. public InputStream openInputStream(String uri) throws IOException {
  18. // TODO:
  19. throw new IOException("File not found: " + uri);
  20. }
  21. @Override
  22. public String store(String extName, InputStream input) throws IOException {
  23. // TODO:
  24. throw new IOException("Unable to access cloud storage.");
  25. }
  26. }

注意到LocalStorageService使用了条件装配@Profile("default"),即默认启用LocalStorageService,而CloudStorageService使用了条件装配@Profile("!default"),即非default环境时,自动启用CloudStorageService。这样,一套代码,就实现了不同环境启用不同的配置。

练习

使用Profiles - 图1下载练习:使用Profile启动Spring Boot应用 (推荐使用IDE练习插件快速下载)

小结

Spring Boot允许在一个配置文件中针对不同Profile进行配置;

Spring Boot在未指定Profile时默认为default

读后有收获可以支付宝请作者喝咖啡,读后有疑问请加微信群讨论:

使用Profiles - 图2 使用Profiles - 图3