我有一个带有此界面的 feignClient:
@FeignClient(
name = "kmr-api",
url = "${client.kmr.url}",
configuration = KmrClient.KmrClientConfiguration.class
)
public interface KmrClient {
@Configuration
class KmrClientConfiguration {
@Value("${client.kmr.access-token-uri}")
private String accessTokenUri;
@Value("${client.kmr.client-id}")
private String clientId;
// ...
@Bean
public Feign.Builder feignBuilder() {
return Feign.builder()
.retryer(Retryer.NEVER_RETRY)
.options(new Request.Options(connectTimeoutMillis, readTimeoutMillis));
}
private OAuth2ProtectedResourceDetails resource() {
ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails();
resourceDetails.setAccessTokenUri(this.accessTokenUri);
resourceDetails.setClientId(this.clientId);
resourceDetails.setClientSecret(this.clientSecret);
// ...
return resourceDetails;
}
@Component
class KmrApiBrandRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("brand", "renault");
}
}
}
@RequestMapping(value = "/vehicle/summary", method = RequestMethod.POST)
VehicleSummary getVehicleSummary(@RequestBody KmrVehicleId2 kmrVehicleId);
}
以及相关的属性,例如
# Feign service
client.kmr.url=https://example/api/v1
client.kmr.access-token-uri=https://example/oauth2/access_token
client.kmr.client-id=myclientid
现在,我需要此 KmrClient 的多个实例,它们具有完全相同的界面和配置,但具有不同的属性。例如,我正在尝试找出可以像这样解决的问题:
# Feign service
client.kmr[0].url=https://example/api/v1
client.kmr[0].access-token-uri=https://example/oauth2/access_token
client.kmr[0].client-id=myclientid
# Feign service
client.kmr[1].url=https://example/api/v1
client.kmr[1].access-token-uri=https://example/oauth2/access_token
client.kmr[1].client-id=myclientid
# there are 4 instances in total, one per region supported in app
从这里我不知道如何加载 client.kmr[1] 中的每个属性并将它们连接到不同的配置和客户端。
我怎样才能达到这个结果?
最佳答案
这是我最终找到的解决方案:
首先,我创建了自己的配置类来保存属性值:
@ConfigurationProperties(prefix = "client.kdiag")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class KdiagClientsProperties {
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class KdiagClientProperties {
String url;
String accessTokenUri;
String clientId;
String clientSecret;
String scope;
int connectTimeoutMillis = 10000;
int readTimeoutMillis = 15000;
}
Map<String, KdiagClientProperties> regions;
}
.properties
看起来像这样:
client.kdiag.regions.EU.url=https://example.io/diag/api/v1
client.kdiag.regions.EU.access-token-uri=https://example.org/auth/oauth2/stuff/access_token
client.kdiag.regions.EU.client-id=redacted
client.kdiag.regions.EU.client-secret=redacted
client.kdiag.regions.EU.scope=scope1,scope2
client.kdiag.regions.RU.url=https://example.io/diag/api/v1
client.kdiag.regions.RU.access-token-uri=https://example.org/auth/oauth2/stuff/access_token
client.kdiag.regions.RU.client-id=redacted
client.kdiag.regions.RU.client-secret=redacted
client.kdiag.regions.RU.scope=scope1,scope2
然后我创建了一个名为 KmrClients 的类,用于访问读取这些属性的所有客户端实例:
/**
* Allow requesting each Kdiag region.
*
* Get the kdiag client with @{@link KdiagClients#clientByRegion(KdisRegion)}
*/
@Order(SecurityProperties.IGNORED_ORDER)
@Configuration
@EnableConfigurationProperties(value = {KdiagClientsProperties.class})
@Import(FeignClientsConfiguration.class) // We need these to reinject decoder, encoder, contract in constructor
@Slf4j
public class KdiagClients {
private Map<String, KdiagClient> clients;
/**
* builds the OAuth2 configuration for a client
*/
private OAuth2ProtectedResourceDetails resource(KdiagClientsProperties.KdiagClientProperties properties) {
ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails();
resourceDetails.setAccessTokenUri(properties.getAccessTokenUri());
resourceDetails.setClientId(properties.getClientId());
resourceDetails.setClientSecret(properties.getClientSecret());
resourceDetails.setScope(Arrays.asList(properties.getScope().split(",")));
resourceDetails.setGrantType("client_credentials");
return resourceDetails;
}
@Autowired
public KdiagClients(
KdiagClientsProperties properties,
Decoder decoder,
Encoder encoder,
Contract contract
) {
Assert.notEmpty(properties.regions, "No KDiag region clients defined in properties (ex: kdiag.client.regions.EU etc) ");
log.info("found kdiag clients for regions:" + properties.regions.keySet().toString());
this.clients = properties.getRegions().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, clientPropertiesEntry -> {
return Feign.builder()
.retryer(Retryer.NEVER_RETRY)
.options(new Request.Options(clientPropertiesEntry.getValue().getConnectTimeoutMillis(), clientPropertiesEntry.getValue().getReadTimeoutMillis()))
.requestInterceptor(new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), resource(clientPropertiesEntry.getValue())))
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.target(KdiagClient.class, clientPropertiesEntry.getValue().getUrl());
}));
}
public KdiagClient clientByRegion(KdisRegion region) {
if (this.clients.containsKey(region.name())) {
return this.clients.get(region.name());
} else {
throw new NoKdiagClientForRegionException(region);
}
}
}
最后注入(inject)KdiagClients就可以使用了:
this.kdiagClients.clientByRegion(KdisRegion.EU)
此解决方案有点特定于某个领域,但可以对其进行调整以满足其需要。所以最后的解决方案是:
- 创建自定义配置属性
- 创建一个面向类,手动实例化 FeignClients 读取配置属性
关于spring-boot - spring boot : same feign client interface, 具有不同属性的多个实例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65183172/