spring-boot - spring boot : same feign client interface, 具有不同属性的多个实例

标签 spring-boot spring-cloud-feign

我有一个带有此界面的 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/

相关文章:

java - 创建名称为 'dynamoDB-DynamoDBMapper' 的 bean 时出错

spring-boot - Spring Boot native 缓存 : How to expire/remove cache data by individual keys/elements

spring-boot - RestTemplate 模拟给出 NullPointerException

java - 父ID未保存在子表Spring JPA2 @OneToMany关系中

spring-cloud-feign - 用于 JWT token 验证的 Feign 客户端自定义拦截器

java - 测试@NotNull 时集成测试失败

java - 消息 -"could not read a hi value - you need to populate the table: hibernate_sequence"

java - 如何在Java Spring Boot中从请求 header 获取承载 token ?

spring-boot - FeignClient : SunCertPathBuilderException: unable to find valid certification path to requested target

spring - 没有springboot可以使用 'feign clients'吗?