Quarkus 中如何在运行时动态设置 configKey
结论
@RegisterRestClient(configKey = "...") 中的 configKey 无法在运行时修改。它是注解属性,必须使用编译期常量。Quarkus 会在应用启动时根据这个值注册并配置 REST Client。
需要动态处理时,应根据具体内容选择方案:
- 服务地址发生变化时,使用
RestClientBuilder在运行时创建客户端。 - 认证 Token 发生变化时,通过
ClientHeadersFactory、@ClientHeaderParam或请求过滤器动态添加。 - 配置内容发生变化时,保留固定的
configKey,通过自定义ConfigSource提供配置,不要修改configKey本身。
为什么不能动态设置
下面的写法要求 configKey 是编译期常量:
@RegisterRestClient(configKey = "payment-api.auth-token-api")
public interface AuthTokenClient {
}
Java 不允许在注解属性中使用运行时变量,因此下面的代码无法编译:
String key = loadKeyFromOtherService();
@RegisterRestClient(configKey = key) // 非法:不是编译期常量
public interface AuthTokenClient {
}
configKey 只是 REST Client 查找配置时使用的键,不是服务地址、Token 或其他业务配置值。例如,固定键:
@RegisterRestClient(configKey = "auth-token-api")
可以对应以下配置:
quarkus:
rest-client:
auth-token-api:
url: https://example.com
一般不需要动态改变键名。需要动态处理的通常是这个键对应的配置值。
方案一:运行时动态创建 REST Client
如果另一个服务返回的是目标服务地址,可以不使用 application.yaml 中配置的 REST Client URL,改为通过 RestClientBuilder 在运行时创建客户端。
客户端接口仍可定义为普通的 REST Client 接口:
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/token")
public interface AuthTokenClient {
@GET
@Produces(MediaType.APPLICATION_JSON)
TokenResponse getToken();
}
获取运行时地址并创建客户端:
import jakarta.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.rest.client.RestClientBuilder;
import java.net.URI;
@ApplicationScoped
public class AuthTokenClientFactory {
private final EndpointDiscoveryService discoveryService;
public AuthTokenClientFactory(EndpointDiscoveryService discoveryService) {
this.discoveryService = discoveryService;
}
public AuthTokenClient createClient() {
String baseUrl = discoveryService.getAuthTokenApiUrl();
if (baseUrl == null || baseUrl.isBlank()) {
throw new IllegalStateException("Auth Token API URL is missing");
}
return RestClientBuilder.newBuilder()
.baseUri(URI.create(baseUrl))
.build(AuthTokenClient.class);
}
}
调用方式如下:
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class PaymentService {
private final AuthTokenClientFactory clientFactory;
public PaymentService(AuthTokenClientFactory clientFactory) {
this.clientFactory = clientFactory;
}
public TokenResponse requestToken() {
AuthTokenClient client = clientFactory.createClient();
return client.getToken();
}
}
如果地址不经常变化,可以缓存客户端实例,避免每次请求都重新构建。地址可能变化时,则要处理好缓存刷新、连接资源释放和并发更新。
方案二:固定 configKey,动态提供配置值
如果仍需使用 CDI 注入,可以保留固定的 configKey:
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@RegisterRestClient(configKey = "auth-token-api")
public interface AuthTokenClient {
}
import org.eclipse.microprofile.rest.client.inject.RestClient;
@Inject
@RestClient
AuthTokenClient authTokenClient;
动态提供的应是具体配置,例如:
quarkus.rest-client.auth-token-api.url=https://example.com
配置可以来自环境变量、系统属性或自定义的 MicroProfile Config ConfigSource。
需要注意的是,Quarkus REST Client 通常会在应用启动或 CDI Bean 初始化阶段读取配置。如果远程服务直到应用完全启动后才返回配置,已经注入的客户端不一定会自动刷新。这种情况更适合使用 RestClientBuilder,或者在配置刷新后重新构建客户端。
自定义 ConfigSource 初始化时,不要调用本身依赖该配置的 REST Client,否则可能产生循环依赖。
方案三:动态值实际是认证 Token
从 auth-token-api 这个名称来看,动态变量也可能指 Token。Token 不应写入 configKey,而应作为 HTTP 请求头发送。
可以使用 ClientHeadersFactory:
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.core.MultivaluedHashMap;
import jakarta.ws.rs.core.MultivaluedMap;
import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory;
@ApplicationScoped
public class AuthorizationHeadersFactory implements ClientHeadersFactory {
private final TokenService tokenService;
public AuthorizationHeadersFactory(TokenService tokenService) {
this.tokenService = tokenService;
}
@Override
public MultivaluedMap<String, String> update(
MultivaluedMap<String, String> incomingHeaders,
MultivaluedMap<String, String> outgoingHeaders) {
MultivaluedMap<String, String> headers = new MultivaluedHashMap<>();
headers.putSingle("Authorization", "Bearer " + tokenService.getToken());
return headers;
}
}
然后在客户端接口上注册:
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@RegisterRestClient(configKey = "auth-token-api")
@RegisterClientHeaders(AuthorizationHeadersFactory.class)
public interface AuthTokenClient {
// REST endpoints
}
这样可以保持 configKey 不变,同时在每次请求时动态获取 Token。
注意事项
configKey是配置命名空间,不是动态业务参数。- 对远程服务返回的 URL,应校验协议、主机和允许访问的目标,避免 SSRF 风险。
- 不要在日志中输出 Token、密码或完整的认证请求头。
- 动态创建大量客户端会增加连接池和资源开销。如果地址稳定,应复用客户端。
- 如果远程配置只在启动阶段获取,可以考虑自定义
ConfigSource。如果配置需要在运行期间频繁变化,应优先使用程序化客户端工厂。 - 不同 Quarkus 版本及 REST Client 实现支持的配置项名称可能略有差异,应以当前项目所用版本的配置参考为准。
备注:内容仅供参考。
