使用Profiles
Profile本身是Spring提供的功能,我们在使用条件装配中已经讲到了,Profile表示一个环境的概念,如开发、测试和生产这3个环境:
- native
- test
- production
或者按git分支定义master、dev这些环境:
- master
- dev
在启动一个Spring应用程序的时候,可以传入一个或多个环境,例如:
-Dspring.profiles.active=test,master
大多数情况下,使用一个环境就足够了。
Spring Boot对Profiles的支持在于,可以在application.yml
中为每个环境进行配置。下面是一个示例配置:
spring:
application:
name: ${APP_NAME:unnamed}
datasource:
url: jdbc:hsqldb:file:testdb
username: sa
password:
dirver-class-name: org.hsqldb.jdbc.JDBCDriver
hikari:
auto-commit: false
connection-timeout: 3000
validation-timeout: 3000
max-lifetime: 60000
maximum-pool-size: 20
minimum-idle: 1
pebble:
suffix:
cache: false
server:
port: ${APP_PORT:8080}
---
spring:
config:
activate:
on-profile: test
server:
port: 8000
---
spring:
config:
activate:
on-profile: production
server:
port: 80
pebble:
cache: true
注意到分隔符---
,最前面的配置是默认配置,不需要指定Profile,后面的每段配置都必须以spring.config.activate.on-profile: xxx
开头,表示一个Profile。上述配置默认使用8080端口,但是在test
环境下,使用8000
端口,在production
环境下,使用80
端口,并且启用Pebble的缓存。
如果我们不指定任何Profile,直接启动应用程序,那么Profile实际上就是default
,可以从Spring Boot启动日志看出:
...
2022-11-25T11:10:34.006+08:00 INFO 13537 --- [ main] com.itranswarp.learnjava.Application : No active profile set, falling back to 1 default profile: "default"
上述日志显示未设置Profile,使用默认的Profile为default
。
要以test
环境启动,可输入如下命令:
$ java -Dspring.profiles.active=test -jar springboot-profiles-1.0-SNAPSHOT.jar
...
2022-11-25T11:09:02.946+08:00 INFO 13510 --- [ main] com.itranswarp.learnjava.Application : The following 1 profile is active: "test"
...
2022-11-25T11:09:05.124+08:00 INFO 13510 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8000 (http) with context path ''
...
从日志看到活动的Profile是test
,Tomcat的监听端口是8000
。
通过Profile可以实现一套代码在不同环境启用不同的配置和功能。假设我们需要一个存储服务,在本地开发时,直接使用文件存储即可,但是,在测试和生产环境,需要存储到云端如S3上,如何通过Profile实现该功能?
首先,我们要定义存储接口StorageService
:
public interface StorageService {
// 根据URI打开InputStream:
InputStream openInputStream(String uri) throws IOException;
// 根据扩展名+InputStream保存并返回URI:
String store(String extName, InputStream input) throws IOException;
}
本地存储可通过LocalStorageService
实现:
@Component
@Profile("default")
public class LocalStorageService implements StorageService {
@Value("${storage.local:/var/static}")
String localStorageRootDir;
final Logger logger = LoggerFactory.getLogger(getClass());
private File localStorageRoot;
@PostConstruct
public void init() {
logger.info("Intializing local storage with root dir: {}", this.localStorageRootDir);
this.localStorageRoot = new File(this.localStorageRootDir);
}
@Override
public InputStream openInputStream(String uri) throws IOException {
File targetFile = new File(this.localStorageRoot, uri);
return new BufferedInputStream(new FileInputStream(targetFile));
}
@Override
public String store(String extName, InputStream input) throws IOException {
String fileName = UUID.randomUUID().toString() + "." + extName;
File targetFile = new File(this.localStorageRoot, fileName);
try (OutputStream output = new BufferedOutputStream(new FileOutputStream(targetFile))) {
input.transferTo(output);
}
return fileName;
}
}
而云端存储可通过CloudStorageService
实现:
@Component
@Profile("!default")
public class CloudStorageService implements StorageService {
@Value("${storage.cloud.bucket:}")
String bucket;
@Value("${storage.cloud.access-key:}")
String accessKey;
@Value("${storage.cloud.access-secret:}")
String accessSecret;
final Logger logger = LoggerFactory.getLogger(getClass());
@PostConstruct
public void init() {
// TODO:
logger.info("Initializing cloud storage...");
}
@Override
public InputStream openInputStream(String uri) throws IOException {
// TODO:
throw new IOException("File not found: " + uri);
}
@Override
public String store(String extName, InputStream input) throws IOException {
// TODO:
throw new IOException("Unable to access cloud storage.");
}
}
注意到LocalStorageService
使用了条件装配@Profile("default")
,即默认启用LocalStorageService
,而CloudStorageService
使用了条件装配@Profile("!default")
,即非default
环境时,自动启用CloudStorageService
。这样,一套代码,就实现了不同环境启用不同的配置。
练习
使用Profile启动Spring Boot应用。
小结
Spring Boot允许在一个配置文件中针对不同Profile进行配置;
Spring Boot在未指定Profile时默认为default
。