Quarkus读取配置

quarkus可以从.properties文件中读取配置。

定义配置

在.properties文件中定义配置:

1
2
greeting.message = hello
greeting.name = quarkus

配置源

  1. (400) System properties
  2. (300) Environment variables
  3. (295) 当前工作目录中的.env文件
  4. (260) Quarkus Application configuration file in $PWD/config/application.properties
  5. (250) Quarkus Application configuration file application.properties in classpath
  6. (100) MicroProfile Config configuration file META-INF/microprofile-config.properties in classpath

最终的配置是上面这些源中配置的聚合,quarkus是从低序号到高序号进行merge,序号越高就越靠后加载,包证了系统属性的安全性,也意味着我们可以在高序号源中定义相同的配置来覆盖低序号源中的配置。

System properties

系统属性可以在启动期间将配置传递给应用程序,比如:

1
2
3
4
5
./mvnw quarkus:dev -Dquarkus.datasource.password=youshallnotpass

or

java -Dquarkus.datasource.password=youshallnotpass -jar target/quarkus-app/quarkus-run.jar

Environment variables

就是系统的环境变量。比如:

1
export QUARKUS_DATASOURCE_PASSWORD=youshallnotpass ; java -jar target/quarkus-app/quarkus-run.jar

.env文件

1
QUARKUS_DATASOURCE_PASSWORD=youshallnotpass

一般位于根目录中,并且不推荐加入VersionControl中。

ApplicationConfiguration文件

文件位置
文件内容:
1
2
3
4
5
// 用户可以自定义配置
greeting.message=hello

// 系统扩展也从这里加载配置
quarkus.http.port=9090 
多环境配置

quarkus默认有三个环境配置:

格式:%{profile-name}.config.name

eg:

1
2
quarkus.http.port=9090
%dev.quarkus.http.port=8181

一般情况下,端口号是9090,但是当处于dev环境时则是8181。如果dev环境没有设置值,则将使用默认值

自定义环境配置
1
2
quarkus.http.port=9090
%custom.quarkus.http.port=9999
父配置

可以通过quarkus.config.profile.parent设置一个父配置,如果在当前活跃的配置中找不到某个属性,就会去查找父配置:

1
2
3
4
5
6
7
8
quarkus.profile=dev
quarkus.config.profile.parent=common

%common.quarkus.http.port=9090
%dev.quarkus.http.ssl-port=9443

quarkus.http.port=8080
quarkus.http.ssl-port=8443

解释:当前采用dev环境,父配置是commonquarkus.http.port会先查找dev环境,发现dev没有设置这一项,再去查找父配置,所以最后实际上会采用父配置的值9090;quarkus.http.ssl-port先去查找dev环境,找到了值9443,就不会再去查找父配置,所以最终值是9443。

属性表达式

语法:

eg:

1
2
3
4
remote.host=quarkus.io

// 可以直接使用上面定义好的属性
callable.url=https://${remote.host}/

索引属性

1
2
3
4
5
my.collection=dog,cat,turtle

my.indexed.collection[0]=dog
my.indexed.collection[1]=cat
my.indexed.collection[2]=turtle

自带配置

Quarkus自带的配置属性,其中带锁的是构建时配置,无法在运行时修改。

读取配置信息

注入方式

可以在resource中注入配置信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Path("/greeting")
public class GreetingResource {

    @ConfigProperty(name = "greeting.message")
    String message;

    /**
     * 默认值
     */
    @ConfigProperty(name = "greeting.suffix", defaultValue = "!")
    String suffix;

    /**
     * 可选方式,可以避免空指针
     */
    @ConfigProperty(name = "greeting.name")
    Optional<String> name;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return message + " " + name.orElse("world") + suffix;
    }
}

获取方式

1
2
3
4
5
6
7
8
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
    String message = ConfigProvider.getConfig().getValue("greeting.message", String.class);
    Optional<String> name = ConfigProvider.getConfig().getOptionalValue("greeting.name", String.class);

    return message + " " + name.get();
}

装箱方式

假设有配置:

1
2
greeting.message = hello
greeting.name = quarkus

我们可以使用@ConfigMapping把所有同一前缀的属性装箱成一个接口:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 支持逗号分隔多个
@ConfigMapping(prefix = "greeting")
public interface Greeting {

    // 命名必须与属性一致
    String name();

    String message();

}

然后在调用时就可以直接注入实现类:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Path("/greeting")
public class GreetingResource {

    @Inject
    Greeting greeting;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return greeting.message() + greeting.name();
    }
}

嵌套方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@ConfigMapping(prefix = "server")
public interface Server {
    String host();

    int port();

    Log log();

    interface Log {
        boolean enabled();

        String suffix();

        boolean rotate();
    }
}

覆盖属性名

@WithName

当我们的方法命名与配置文件中的属性名称不一致时,可以使用@WithName注解标明:

配置:

1
2
server.name=localhost
server.port=8080

映射:

1
2
3
4
5
6
7
@ConfigMapping(prefix = "server")
interface Server {
    @WithName("name")
    String host();

    int port();
}
@WithParent

组合方式。

1
2
3
server.host=localhost
server.port=8080
server.name=konoha
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
interface Server {
    @WithParentName
    ServerHostAndPort hostAndPort();

    @WithParentName
    ServerInfo info();
}

interface ServerHostAndPort {
    String host();

    int port();
}

interface ServerInfo {
    String name();
}
驼峰命名策略
1
2
server.the-host=localhost
server.the-port=8080

用-来分隔单词,然后就可以使用驼峰方式命名的方法来映射:

1
2
3
4
5
6
@ConfigMapping(prefix = "server")
interface Server {
    String theHost();

    int thePort();
}
映射方法

配置:

1
foo=foo

映射:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@ConfigMapping
public interface Converters {
    @WithConverter(FooBarConverter.class)
    String foo();
}

public static class FooBarConverter implements Converter<String> {
    @Override
    public String convert(final String value) {
        return "bar";
    }
}

当我们使用 foo()方法时,它并不会输出foo,而是输出bar。

映射集合

配置:

1
2
3
4
5
6
server.environments[0].name=dev
server.environments[0].apps[0].name=rest
server.environments[0].apps[0].services=bookstore,registration
server.environments[0].apps[0].databases=pg,h2
server.environments[0].apps[1].name=batch
server.environments[0].apps[1].services=stock,warehous

映射:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@ConfigMapping(prefix = "server")
public interface ServerCollections {
    Set<Environment> environments();

    interface Environment {
        String name();

        List<App> apps();

        interface App {
            String name();

            List<String> services();

            Optional<List<String>> databases();
        }
    }
}
映射Map

配置:

1
2
3
4
5
server.host=localhost
server.port=8080
server.form.login-page=login.html
server.form.error-page=error.html
server.form.landing-page=index.html

映射:

1
2
3
4
5
6
7
8
@ConfigMapping(prefix = "server")
public interface Server {
    String host();

    int port();

    Map<String, String> form();
}
默认值
1
2
3
4
5
6
7
public interface Defaults {
    @WithDefault("foo")
    String foo();

    @WithDefault("bar")
    String bar();
}
验证配置
1
2
3
4
5
6
7
8
@ConfigMapping(prefix = "server")
interface Server {
    @Size(min = 2, max = 20)
    String host();

    @Max(10000)
    int port();
}

需要quarkus-hibernate-validator扩展。

扩展配置

一般情况下,我们在实际的工作中可能不止dev、test、prod三种环境,可能还需要更多的配置。

自定义配置源

扩展ConfigSource

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package org.acme.config;

public class InMemoryConfigSource implements ConfigSource {

    /**
     * 内存缓存
     */
    private static final Map<String, String> configuration = new HashMap<>();

    static {
        configuration.put("my.prop", "1234");
    }

    /**
     * 定义加载序号
     */
    @Override
    public int getOrdinal() {
        return 275;
    }

    @Override
    public Set<String> getPropertyNames() {
        return configuration.keySet();
    }

    @Override
    public String getValue(final String propertyName) {
        return configuration.get(propertyName);
    }

    /**
     * 返回该配置的名称
     */
    @Override
    public String getName() {
        return InMemoryConfigSource.class.getSimpleName();
    }
}

注册配置源

接下来在META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource注册配置:

1
org.acme.config.InMemoryConfigSource

初始化配置源

当 Quarkus 应用程序启动时,可以初始化两次。一次用于静态初始化,第二次用于运行时初始化

静态初始化