Make feign.Target.url refreshable (#766)
This commit is contained in:
@@ -179,6 +179,7 @@ spring:
|
||||
client:
|
||||
config:
|
||||
feignName:
|
||||
url: http://remote-service.com
|
||||
connectTimeout: 5000
|
||||
readTimeout: 5000
|
||||
loggerLevel: full
|
||||
@@ -794,7 +795,13 @@ spring.cloud.openfeign.autoconfiguration.jackson.enabled=false
|
||||
See `org.springframework.cloud.openfeign.FeignAutoConfiguration.FeignJacksonConfiguration` for details.
|
||||
|
||||
=== Spring `@RefreshScope` Support
|
||||
If Feign client refresh is enabled, each feign client is created with `feign.Request.Options` as a refresh-scoped bean. This means properties such as `connectTimeout` and `readTimeout` can be refreshed against any Feign client instance through `POST /actuator/refresh`.
|
||||
If Feign client refresh is enabled, each Feign client is created with:
|
||||
|
||||
* `feign.Request.Options` as a refresh-scoped bean. This means properties such as `connectTimeout` and `readTimeout` can be refreshed against any Feign client instance.
|
||||
* A url wrapped under `org.springframework.cloud.openfeign.RefreshableUrl`. This means the URL of Feign client, if defined
|
||||
with `spring.cloud.openfeign.client.config.{feignName}.url` property, can be refreshed against any Feign client instance.
|
||||
|
||||
You can refresh these properties through `POST /actuator/refresh`.
|
||||
|
||||
By default, refresh behavior in Feign clients is disabled. Use the following property to enable refresh behavior:
|
||||
[source,java]
|
||||
@@ -851,6 +858,35 @@ Alternatively, you can use `LoadBalancerFeignRequestTransformer.DEFAULT_ORDER` t
|
||||
spring.cloud.loadbalancer.x-forwarded.enabled=true
|
||||
----
|
||||
|
||||
=== Supported Ways To Provide URL To A Feign Client
|
||||
You can provide a URL to a Feign client in any of the following ways:
|
||||
|
||||
|===
|
||||
|Case |Example |Details
|
||||
|
||||
|The URL is provided in the `@FeignClient` annotation.
|
||||
|`@FeignClient(name="testClient", url="http://localhost:8081")`
|
||||
|The URL is resolved from the `url` attribute of the annotation, without load-balancing.
|
||||
|
||||
|The URL is provided in the `@FeignClient` annotation and in the
|
||||
configuration properties.
|
||||
|`@FeignClient(name="testClient", url="http://localhost:8081")` and the property defined in `application.yml` as
|
||||
`spring.cloud.openfeign.client.config.testClient.url=http://localhost:8081`
|
||||
|The URL is resolved from the `url` attribute of the annotation, without load-balancing.
|
||||
The URL provided in the configuration properties remains unused.
|
||||
|
||||
|The URL is not provided in the `@FeignClient` annotation but is provided in configuration properties.
|
||||
| `@FeignClient(name="testClient")` and the property defined in `application.yml` as
|
||||
`spring.cloud.openfeign.client.config.testClient.url=http://localhost:8081`
|
||||
|The URL is resolved from configuration properties, without load-balancing. If
|
||||
`spring.cloud.openfeign.client.refresh-enabled=true`, then the URL defined in configuration properties can be refreshed as described in <<Spring `@RefreshScope` Support,Spring RefreshScope Support>>.
|
||||
|
||||
|The URL is neither provided in the `@FeignClient` annotation nor in configuration properties.
|
||||
|`@FeignClient(name="testClient")`
|
||||
|The URL is resolved from `name` attribute of annotation, with load balancing.
|
||||
|
||||
|===
|
||||
|
||||
== Configuration properties
|
||||
|
||||
To see the list of all Spring Cloud OpenFeign related configuration properties please check link:appendix.html[the Appendix page].
|
||||
|
||||
@@ -411,8 +411,7 @@ public class FeignClientFactoryBean
|
||||
FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
|
||||
: applicationContext.getBean(FeignContext.class);
|
||||
Feign.Builder builder = feign(context);
|
||||
|
||||
if (!StringUtils.hasText(url)) {
|
||||
if (!StringUtils.hasText(url) && !isUrlAvailableInConfig(contextId)) {
|
||||
|
||||
if (LOG.isInfoEnabled()) {
|
||||
LOG.info("For '" + name + "' URL not provided. Will try picking an instance via load-balancing.");
|
||||
@@ -448,7 +447,7 @@ public class FeignClientFactoryBean
|
||||
applyBuildCustomizers(context, builder);
|
||||
|
||||
Targeter targeter = get(context, Targeter.class);
|
||||
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
|
||||
return targeter.target(this, builder, context, (HardCodedTarget<T>) resolveTarget(context, contextId, url));
|
||||
}
|
||||
|
||||
private String cleanPath() {
|
||||
@@ -467,6 +466,38 @@ public class FeignClientFactoryBean
|
||||
return path;
|
||||
}
|
||||
|
||||
private <T> HardCodedTarget<T> resolveTarget(FeignContext context, String contextId, String url) {
|
||||
if (StringUtils.hasText(url)) {
|
||||
return new HardCodedTarget(type, name, url);
|
||||
}
|
||||
|
||||
if (refreshableClient) {
|
||||
RefreshableUrl refreshableUrl = context.getInstance(contextId,
|
||||
RefreshableUrl.class.getCanonicalName() + "-" + contextId, RefreshableUrl.class);
|
||||
if (Objects.nonNull(refreshableUrl) && StringUtils.hasText(refreshableUrl.getUrl())) {
|
||||
return new RefreshableHardCodedTarget<>(type, name, refreshableUrl);
|
||||
}
|
||||
}
|
||||
FeignClientProperties.FeignClientConfiguration config = findConfigByKey(contextId);
|
||||
if (Objects.isNull(config) || !StringUtils.hasText(config.getUrl())) {
|
||||
throw new IllegalStateException(
|
||||
"Provide Feign client URL either in @FeignClient() or in config properties.");
|
||||
}
|
||||
|
||||
return new HardCodedTarget(type, name, FeignClientsRegistrar.getUrl(config.getUrl()));
|
||||
}
|
||||
|
||||
private boolean isUrlAvailableInConfig(String contextId) {
|
||||
FeignClientProperties.FeignClientConfiguration config = findConfigByKey(contextId);
|
||||
return Objects.nonNull(config) && StringUtils.hasText(config.getUrl());
|
||||
}
|
||||
|
||||
private FeignClientProperties.FeignClientConfiguration findConfigByKey(String configKey) {
|
||||
FeignClientProperties properties = beanFactory != null ? beanFactory.getBean(FeignClientProperties.class)
|
||||
: applicationContext.getBean(FeignClientProperties.class);
|
||||
return properties.getConfig().get(configKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return type;
|
||||
|
||||
@@ -42,6 +42,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
* @author Jonatan Ivanov
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Hyeonmin Park
|
||||
* @author Jasbir Singh
|
||||
*/
|
||||
@ConfigurationProperties("spring.cloud.openfeign.client")
|
||||
public class FeignClientProperties {
|
||||
@@ -147,6 +148,12 @@ public class FeignClientProperties {
|
||||
|
||||
private Boolean followRedirects;
|
||||
|
||||
/**
|
||||
* Allows setting Feign client host URL. This value will only be taken into
|
||||
* account if the url is not set in the @FeignClient annotation.
|
||||
*/
|
||||
private String url;
|
||||
|
||||
public Logger.Level getLoggerLevel() {
|
||||
return loggerLevel;
|
||||
}
|
||||
@@ -283,6 +290,14 @@ public class FeignClientProperties {
|
||||
this.followRedirects = followRedirects;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
@@ -303,14 +318,14 @@ public class FeignClientProperties {
|
||||
&& Objects.equals(defaultQueryParameters, that.defaultQueryParameters)
|
||||
&& Objects.equals(capabilities, that.capabilities)
|
||||
&& Objects.equals(queryMapEncoder, that.queryMapEncoder) && Objects.equals(metrics, that.metrics)
|
||||
&& Objects.equals(followRedirects, that.followRedirects);
|
||||
&& Objects.equals(followRedirects, that.followRedirects) && Objects.equals(url, that.url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(loggerLevel, connectTimeout, readTimeout, retryer, errorDecoder, requestInterceptors,
|
||||
dismiss404, encoder, decoder, contract, exceptionPropagationPolicy, defaultQueryParameters,
|
||||
defaultRequestHeaders, capabilities, queryMapEncoder, metrics, followRedirects);
|
||||
defaultRequestHeaders, capabilities, queryMapEncoder, metrics, followRedirects, url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -255,7 +255,8 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
|
||||
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
|
||||
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
|
||||
|
||||
registerOptionsBeanDefinition(registry, contextId);
|
||||
registerRefreshableBeanDefinition(registry, contextId, Request.Options.class, OptionsFactoryBean.class);
|
||||
registerRefreshableBeanDefinition(registry, contextId, RefreshableUrl.class, RefreshableUrlFactoryBean.class);
|
||||
}
|
||||
|
||||
private void validate(Map<String, Object> attributes) {
|
||||
@@ -420,16 +421,17 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is meant to create {@link Request.Options} beans definition with
|
||||
* refreshScope.
|
||||
* This method registers beans definition with refreshScope.
|
||||
* @param registry spring bean definition registry
|
||||
* @param contextId name of feign client
|
||||
* @param beanType type of bean
|
||||
* @param factoryBeanType points to a relevant bean factory
|
||||
*/
|
||||
private void registerOptionsBeanDefinition(BeanDefinitionRegistry registry, String contextId) {
|
||||
private void registerRefreshableBeanDefinition(BeanDefinitionRegistry registry, String contextId, Class<?> beanType,
|
||||
Class<?> factoryBeanType) {
|
||||
if (isClientRefreshEnabled()) {
|
||||
String beanName = Request.Options.class.getCanonicalName() + "-" + contextId;
|
||||
BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(OptionsFactoryBean.class);
|
||||
String beanName = beanType.getCanonicalName() + "-" + contextId;
|
||||
BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(factoryBeanType);
|
||||
definitionBuilder.setScope("refresh");
|
||||
definitionBuilder.addPropertyValue("contextId", contextId);
|
||||
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(definitionBuilder.getBeanDefinition(),
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2013-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import feign.Target;
|
||||
|
||||
/**
|
||||
* This target provides url wrapped under {@link Target}.
|
||||
*
|
||||
* @author Jasbir Singh
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class RefreshableHardCodedTarget<T> extends Target.HardCodedTarget<T> {
|
||||
|
||||
private RefreshableUrl refreshableUrl;
|
||||
|
||||
public RefreshableHardCodedTarget(Class type, String name, RefreshableUrl refreshableUrl) {
|
||||
super(type, name, refreshableUrl.getUrl());
|
||||
this.refreshableUrl = refreshableUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String url() {
|
||||
return refreshableUrl.getUrl();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2013-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
/**
|
||||
* This class wraps url inside an object so that relevant proxy instance can be created
|
||||
* using {@link RefreshableUrlFactoryBean}.
|
||||
*
|
||||
* @author Jasbir Singh
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class RefreshableUrl {
|
||||
|
||||
private final String url;
|
||||
|
||||
public RefreshableUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2013-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* This factory bean creates {@link RefreshableUrl} instance as per the applicable
|
||||
* configurations.
|
||||
*
|
||||
* @author Jasbir Singh
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class RefreshableUrlFactoryBean implements FactoryBean<RefreshableUrl>, ApplicationContextAware {
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private String contextId;
|
||||
|
||||
private RefreshableUrl refreshableUrl;
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return RefreshableUrl.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RefreshableUrl getObject() throws Exception {
|
||||
if (refreshableUrl != null) {
|
||||
return refreshableUrl;
|
||||
}
|
||||
|
||||
FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);
|
||||
if (Objects.isNull(properties.getConfig())) {
|
||||
return new RefreshableUrl(null);
|
||||
}
|
||||
FeignClientProperties.FeignClientConfiguration configuration = properties.getConfig().get(contextId);
|
||||
if (Objects.isNull(configuration) || !StringUtils.hasText(configuration.getUrl())) {
|
||||
return new RefreshableUrl(null);
|
||||
}
|
||||
|
||||
refreshableUrl = new RefreshableUrl(FeignClientsRegistrar.getUrl(configuration.getUrl()));
|
||||
return refreshableUrl;
|
||||
}
|
||||
|
||||
public void setContextId(String contextId) {
|
||||
this.contextId = contextId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2013-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import feign.Target;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Jasbir Singh
|
||||
*/
|
||||
@SpringBootTest
|
||||
@TestPropertySource("classpath:feign-properties.properties")
|
||||
@DirtiesContext
|
||||
class NonRefreshableFeignClientUrlTests {
|
||||
|
||||
@Autowired
|
||||
private Application.FeignClientWithFixUrl feignClientWithFixUrl;
|
||||
|
||||
@Autowired
|
||||
private Application.ConfigBasedClient configBasedClient;
|
||||
|
||||
@Autowired
|
||||
private Application.NameBasedUrlClient nameBasedUrlClient;
|
||||
|
||||
@Test
|
||||
void shouldInstantiateFeignClientWhenUrlFromFeignClientUrl() {
|
||||
UrlTestClient.UrlResponseForTests response = feignClientWithFixUrl.fixPath();
|
||||
assertThat(response.getUrl()).isEqualTo("http://localhost:8081/fixPath");
|
||||
assertThat(response.getTargetType()).isEqualTo(Target.HardCodedTarget.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateFeignClientWhenUrlFromFeignClientUrlGivenPreferenceOverProperties() {
|
||||
UrlTestClient.UrlResponseForTests response = feignClientWithFixUrl.fixPath();
|
||||
assertThat(response.getUrl()).isEqualTo("http://localhost:8081/fixPath");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldInstantiateFeignClientWhenUrlFromProperties() {
|
||||
UrlTestClient.UrlResponseForTests response = configBasedClient.test();
|
||||
assertThat(response.getUrl()).isEqualTo("http://localhost:9999/test");
|
||||
assertThat(response.getTargetType()).isEqualTo(Target.HardCodedTarget.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateFeignClientWhenUrlFromFeignClientName() {
|
||||
UrlTestClient.UrlResponseForTests response = nameBasedUrlClient.test();
|
||||
assertThat(response.getUrl()).isEqualTo("http://nameBasedClient/test");
|
||||
assertThat(response.getTargetType()).isEqualTo(Target.HardCodedTarget.class);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
@EnableConfigurationProperties(FeignClientProperties.class)
|
||||
@EnableFeignClients(clients = { Application.FeignClientWithFixUrl.class, Application.ConfigBasedClient.class,
|
||||
Application.NameBasedUrlClient.class })
|
||||
protected static class Application {
|
||||
|
||||
@Bean
|
||||
UrlTestClient client() {
|
||||
return new UrlTestClient();
|
||||
}
|
||||
|
||||
@FeignClient(name = "feignClientWithFixUrl", url = "http://localhost:8081")
|
||||
protected interface FeignClientWithFixUrl {
|
||||
|
||||
@GetMapping("/fixPath")
|
||||
UrlTestClient.UrlResponseForTests fixPath();
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "configBasedClient")
|
||||
protected interface ConfigBasedClient {
|
||||
|
||||
@GetMapping("/test")
|
||||
UrlTestClient.UrlResponseForTests test();
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "nameBasedClient")
|
||||
protected interface NameBasedUrlClient {
|
||||
|
||||
@GetMapping("/test")
|
||||
UrlTestClient.UrlResponseForTests test();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright 2013-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import feign.Target;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.context.scope.refresh.RefreshScope;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Jasbir Singh
|
||||
*/
|
||||
@SpringBootTest
|
||||
@TestPropertySource("classpath:feign-refreshable-properties.properties")
|
||||
@DirtiesContext
|
||||
class RefreshableFeignClientUrlTests {
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Autowired
|
||||
private RefreshScope refreshScope;
|
||||
|
||||
@Autowired
|
||||
private RefreshableFeignClientUrlTests.Application.RefreshableClientWithFixUrl refreshableClientWithFixUrl;
|
||||
|
||||
@Autowired
|
||||
private RefreshableFeignClientUrlTests.Application.RefreshableUrlClient refreshableUrlClient;
|
||||
|
||||
@Autowired
|
||||
private Application.RefreshableUrlClientForContextRefreshCase refreshableUrlClientForContextRefreshCase;
|
||||
|
||||
@Autowired
|
||||
private Application.NameBasedUrlClient nameBasedUrlClient;
|
||||
|
||||
@Autowired
|
||||
private FeignClientProperties clientProperties;
|
||||
|
||||
@Test
|
||||
void shouldInstantiateFeignClientWhenUrlFromFeignClientUrl() {
|
||||
UrlTestClient.UrlResponseForTests response = refreshableClientWithFixUrl.fixPath();
|
||||
assertThat(response.getUrl()).isEqualTo("http://localhost:8081/fixPath");
|
||||
assertThat(response.getTargetType()).isEqualTo(Target.HardCodedTarget.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateFeignClientWhenUrlFromFeignClientUrlGivenPreferenceOverProperties() {
|
||||
UrlTestClient.UrlResponseForTests response = refreshableClientWithFixUrl.fixPath();
|
||||
assertThat(response.getUrl()).isEqualTo("http://localhost:8081/fixPath");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldInstantiateFeignClientWhenUrlFromProperties() {
|
||||
UrlTestClient.UrlResponseForTests response = refreshableUrlClient.refreshable();
|
||||
assertThat(response.getUrl()).isEqualTo("http://localhost:8082/refreshable");
|
||||
assertThat(response.getTargetType()).isEqualTo(RefreshableHardCodedTarget.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateFeignClientWhenUrlFromPropertiesAndThenUpdateUrlWhenContextRefresh() {
|
||||
UrlTestClient.UrlResponseForTests response = refreshableUrlClientForContextRefreshCase.refreshable();
|
||||
assertThat(response.getUrl()).isEqualTo("http://localhost:8080/refreshable");
|
||||
|
||||
clientProperties.getConfig().get("refreshableClient").setUrl("http://localhost:8888/");
|
||||
refreshScope.refreshAll();
|
||||
response = refreshableUrlClient.refreshable();
|
||||
assertThat(response.getUrl()).isEqualTo("http://localhost:8888/refreshable");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateFeignClientWhenUrlFromFeignClientName() {
|
||||
UrlTestClient.UrlResponseForTests response = nameBasedUrlClient.nonRefreshable();
|
||||
assertThat(response.getUrl()).isEqualTo("http://nameBasedClient/nonRefreshable");
|
||||
assertThat(response.getTargetType()).isEqualTo(Target.HardCodedTarget.class);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
@EnableConfigurationProperties(FeignClientProperties.class)
|
||||
@EnableFeignClients(clients = { Application.RefreshableUrlClient.class, Application.NameBasedUrlClient.class,
|
||||
Application.RefreshableClientWithFixUrl.class,
|
||||
Application.RefreshableUrlClientForContextRefreshCase.class })
|
||||
protected static class Application {
|
||||
|
||||
@Bean
|
||||
UrlTestClient client() {
|
||||
return new UrlTestClient();
|
||||
}
|
||||
|
||||
@FeignClient(name = "refreshableClientWithFixUrl", url = "http://localhost:8081")
|
||||
protected interface RefreshableClientWithFixUrl {
|
||||
|
||||
@GetMapping("/fixPath")
|
||||
UrlTestClient.UrlResponseForTests fixPath();
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "refreshableClient")
|
||||
protected interface RefreshableUrlClient {
|
||||
|
||||
@GetMapping("/refreshable")
|
||||
UrlTestClient.UrlResponseForTests refreshable();
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "refreshableClientForContextRefreshCase")
|
||||
protected interface RefreshableUrlClientForContextRefreshCase {
|
||||
|
||||
@GetMapping("/refreshable")
|
||||
UrlTestClient.UrlResponseForTests refreshable();
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "nameBasedClient")
|
||||
protected interface NameBasedUrlClient {
|
||||
|
||||
@GetMapping("/nonRefreshable")
|
||||
UrlTestClient.UrlResponseForTests nonRefreshable();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2013-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import feign.Client;
|
||||
import feign.Request;
|
||||
import feign.Response;
|
||||
|
||||
/**
|
||||
* @author Jasbir Singh
|
||||
*/
|
||||
public class UrlTestClient implements Client {
|
||||
|
||||
protected static ObjectMapper mapper;
|
||||
|
||||
static {
|
||||
mapper = new ObjectMapper();
|
||||
mapper.registerModule(new JavaTimeModule()).configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response execute(Request request, Request.Options options) throws IOException {
|
||||
return Response.builder().status(200).request(request).headers(headers()).body(prepareResponse(request))
|
||||
.build();
|
||||
}
|
||||
|
||||
private Map<String, Collection<String>> headers() {
|
||||
Map<String, Collection<String>> headers = new LinkedHashMap<>();
|
||||
headers.put("Content-Type", Collections.singletonList("application/json"));
|
||||
return headers;
|
||||
}
|
||||
|
||||
private byte[] prepareResponse(Request request) {
|
||||
try {
|
||||
UrlResponseForTests response = new UrlResponseForTests(request.url(),
|
||||
request.requestTemplate().feignTarget().getClass());
|
||||
return mapper.writeValueAsString(response).getBytes();
|
||||
}
|
||||
catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
static class UrlResponseForTests {
|
||||
|
||||
private String url;
|
||||
|
||||
private Class<?> targetType;
|
||||
|
||||
UrlResponseForTests() {
|
||||
}
|
||||
|
||||
UrlResponseForTests(String url, Class<?> targetType) {
|
||||
this.url = url;
|
||||
this.targetType = targetType;
|
||||
}
|
||||
|
||||
void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public Class<?> getTargetType() {
|
||||
return targetType;
|
||||
}
|
||||
|
||||
public void setTargetType(Class<?> targetType) {
|
||||
this.targetType = targetType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UrlResponseForTests{" + "url='" + url + '\'' + ", targetType=" + targetType + '}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,3 +25,5 @@ spring.cloud.openfeign.client.config.unwrap.exceptionPropagationPolicy=unwrap
|
||||
spring.cloud.openfeign.client.config.readTimeout.readTimeout=1000
|
||||
spring.cloud.openfeign.client.config.connectTimeout.connectTimeout=1000
|
||||
spring.cloud.openfeign.client.config.default.followRedirects=false
|
||||
spring.cloud.openfeign.client.config.feignClientWithFixUrl.url=http://localhost:8888
|
||||
spring.cloud.openfeign.client.config.configBasedClient.url=http://localhost:9999
|
||||
|
||||
@@ -8,3 +8,6 @@ spring.cloud.openfeign.client.config.default.readTimeout=5000
|
||||
spring.cloud.openfeign.client.config.default.loggerLevel=full
|
||||
spring.cloud.openfeign.client.config.connectTimeout.connectTimeout=2000
|
||||
spring.cloud.openfeign.client.config.readTimeout.readTimeout=2000
|
||||
spring.cloud.openfeign.client.config.refreshableClient.url=http://localhost:8082
|
||||
spring.cloud.openfeign.client.config.refreshableClientWithFixUrl.url=http://localhost:8888
|
||||
spring.cloud.openfeign.client.config.refreshableClientForContextRefreshCase.url=http://localhost:8080
|
||||
|
||||
Reference in New Issue
Block a user