Make feign.Request.Options as refreshable beans inside Feign clients (#526)

This commit is contained in:
Jasbir
2021-04-19 21:57:28 +05:30
committed by GitHub
parent 96268d16da
commit e47255e79d
10 changed files with 486 additions and 9 deletions

View File

@@ -0,0 +1,185 @@
/*
* Copyright 2013-2021 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.concurrent.TimeUnit;
import feign.Request;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
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 org.springframework.web.context.support.GenericWebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Jasbir Singh
*/
@SpringBootTest
@TestPropertySource("classpath:feign-refreshable-properties.properties")
@DirtiesContext
public class FeignClientWithRefreshableOptionsTest {
@Autowired
private ApplicationContext applicationContext;
@Autowired
private RefreshScope refreshScope;
@Autowired
private Application.RefreshableClient refreshableClient;
@Autowired
private Application.ReadTimeoutClient readTimeoutClient;
@Autowired
private Application.ConnectTimeoutClient connectTimeoutClient;
@Autowired
private Application.OverrideOptionsClient overrideOptionsClient;
@Autowired
private FeignClientProperties clientProperties;
@Test
public void overridedOptionsBeanShouldBePresentInsteadOfRefreshable() {
OptionsTestClient.OptionsResponseForTests options = overrideOptionsClient.override();
assertConnectionAndReadTimeout(options, 1, 1);
}
@Test
public void refreshScopeBeanDefinitionShouldBePresent() {
BeanDefinition beanDefinition = ((GenericWebApplicationContext) applicationContext)
.getBeanDefinition(Request.Options.class.getCanonicalName() + "-" + "refreshableClient");
BeanDefinition originBeanDefinition = beanDefinition.getOriginatingBeanDefinition();
assertThat(originBeanDefinition.getBeanClassName()).isEqualTo(OptionsFactoryBean.class.getCanonicalName());
assertThat(originBeanDefinition.getScope()).isEqualTo("refresh");
}
@Test
public void withConfigDefaultConnectTimeoutAndReadTimeout() {
OptionsTestClient.OptionsResponseForTests options = refreshableClient.refreshable();
assertConnectionAndReadTimeout(options, 5000, 5000);
}
@Test
public void readTimeoutShouldWorkWhenConnectTimeoutNotSet() {
OptionsTestClient.OptionsResponseForTests options = readTimeoutClient.readTimeout();
assertConnectionAndReadTimeout(options, 5000, 2000);
}
@Test
public void connectTimeoutShouldWorkWhenReadTimeoutNotSet() {
OptionsTestClient.OptionsResponseForTests options = connectTimeoutClient.connectTimeout();
assertConnectionAndReadTimeout(options, 2000, 5000);
}
@Test
public void connectTimeoutShouldNotChangeWithoutContextRefresh() {
OptionsTestClient.OptionsResponseForTests options = connectTimeoutClient.connectTimeout();
assertConnectionAndReadTimeout(options, 2000, 5000);
clientProperties.getConfig().get("connectTimeout").setConnectTimeout(5000);
options = connectTimeoutClient.connectTimeout();
assertConnectionAndReadTimeout(options, 2000, 5000);
}
@Test
public void connectTimeoutShouldChangeAfterContextRefresh() {
OptionsTestClient.OptionsResponseForTests options = connectTimeoutClient.connectTimeout();
assertConnectionAndReadTimeout(options, 2000, 5000);
clientProperties.getConfig().get("connectTimeout").setConnectTimeout(5000);
refreshScope.refreshAll();
options = connectTimeoutClient.connectTimeout();
assertConnectionAndReadTimeout(options, 5000, 5000);
}
private void assertConnectionAndReadTimeout(OptionsTestClient.OptionsResponseForTests options,
int expectedConnectTimeoutInMillis, int expectedReadTimeoutInMillis) {
assertThat(options.getConnectTimeout()).isEqualTo(expectedConnectTimeoutInMillis);
assertThat(options.getReadTimeout()).isEqualTo(expectedReadTimeoutInMillis);
}
@Configuration
@EnableAutoConfiguration
@EnableConfigurationProperties(FeignClientProperties.class)
@EnableFeignClients(clients = { Application.OverrideOptionsClient.class, Application.RefreshableClient.class,
Application.ReadTimeoutClient.class, Application.ConnectTimeoutClient.class })
protected static class Application {
@Bean
OptionsTestClient client() {
return new OptionsTestClient();
}
@FeignClient(name = "overrideOptionsClient", configuration = OverrideConfig.class)
protected interface OverrideOptionsClient {
@GetMapping("/override")
OptionsTestClient.OptionsResponseForTests override();
}
@FeignClient(name = "refreshableClient")
protected interface RefreshableClient {
@GetMapping("/refreshable")
OptionsTestClient.OptionsResponseForTests refreshable();
}
@FeignClient(name = "readTimeout")
protected interface ReadTimeoutClient {
@GetMapping("/readTimeout")
OptionsTestClient.OptionsResponseForTests readTimeout();
}
@FeignClient(name = "connectTimeout")
protected interface ConnectTimeoutClient {
@GetMapping("/connectTimeout")
OptionsTestClient.OptionsResponseForTests connectTimeout();
}
@Configuration
protected class OverrideConfig {
@Bean
public Request.Options options() {
return new Request.Options(1, TimeUnit.MILLISECONDS, 1, TimeUnit.MILLISECONDS, true);
}
}
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2013-2021 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 java.util.concurrent.TimeUnit;
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 OptionsTestClient implements Client {
private 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(options))
.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.Options options) {
try {
OptionsResponseForTests response = new OptionsResponseForTests(options.connectTimeoutMillis(),
TimeUnit.MILLISECONDS, options.readTimeoutMillis(), TimeUnit.MILLISECONDS);
return mapper.writeValueAsString(response).getBytes();
}
catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
static class OptionsResponseForTests {
private long connectTimeout;
private TimeUnit connectTimeoutUnit;
private long readTimeout;
private TimeUnit readTimeoutUnit;
OptionsResponseForTests(long connectTimeout, TimeUnit connectTimeoutUnit, long readTimeout,
TimeUnit readTimeoutUnit) {
this.connectTimeout = connectTimeout;
this.connectTimeoutUnit = connectTimeoutUnit;
this.readTimeout = readTimeout;
this.readTimeoutUnit = readTimeoutUnit;
}
public long getConnectTimeout() {
return connectTimeout;
}
public TimeUnit getConnectTimeoutUnit() {
return connectTimeoutUnit;
}
public long getReadTimeout() {
return readTimeout;
}
public TimeUnit getReadTimeoutUnit() {
return readTimeoutUnit;
}
@Override
public String toString() {
return "OptionsResponseForTests{" + "connectTimeout=" + connectTimeout + ", connectTimeoutUnit="
+ connectTimeoutUnit + ", readTimeout=" + readTimeout + ", readTimeoutUnit=" + readTimeoutUnit
+ '}';
}
}
}

View File

@@ -0,0 +1,10 @@
# This configuration used by test class FeignClientWithRefreshableOptionsTest
logging.level.org.springframework.cloud.openfeign=debug
feign.client.default-to-properties=true
feign.client.default-config=default
feign.client.refresh-enabled=true
feign.client.config.default.connectTimeout=5000
feign.client.config.default.readTimeout=5000
feign.client.config.default.loggerLevel=full
feign.client.config.connectTimeout.connectTimeout=2000
feign.client.config.readTimeout.readTimeout=2000