共计 6659 个字符,预计需要花费 17 分钟才能阅读完成。
序
本文主要研究一下 spring cloud 的 FeignClientBuilder
FeignClientBuilder
spring-cloud-openfeign-core-2.2.0.M1-sources.jar!/org/springframework/cloud/openfeign/FeignClientBuilder.java
public class FeignClientBuilder { | |
private final ApplicationContext applicationContext; | |
public FeignClientBuilder(final ApplicationContext applicationContext) {this.applicationContext = applicationContext;} | |
public <T> Builder<T> forType(final Class<T> type, final String name) {return new Builder<>(this.applicationContext, type, name); | |
} | |
/** | |
* Builder of feign targets. | |
* | |
* @param <T> type of target | |
*/ | |
public static final class Builder<T> { | |
private FeignClientFactoryBean feignClientFactoryBean; | |
private Builder(final ApplicationContext applicationContext, final Class<T> type, | |
final String name) {this.feignClientFactoryBean = new FeignClientFactoryBean(); | |
this.feignClientFactoryBean.setApplicationContext(applicationContext); | |
this.feignClientFactoryBean.setType(type); | |
this.feignClientFactoryBean.setName(FeignClientsRegistrar.getName(name)); | |
this.feignClientFactoryBean.setContextId(FeignClientsRegistrar.getName(name)); | |
// preset default values - these values resemble the default values on the | |
// FeignClient annotation | |
this.url("").path("").decode404(false).fallback(void.class) | |
.fallbackFactory(void.class); | |
} | |
public Builder url(final String url) {this.feignClientFactoryBean.setUrl(FeignClientsRegistrar.getUrl(url)); | |
return this; | |
} | |
public Builder contextId(final String contextId) {this.feignClientFactoryBean.setContextId(contextId); | |
return this; | |
} | |
public Builder path(final String path) {this.feignClientFactoryBean.setPath(FeignClientsRegistrar.getPath(path)); | |
return this; | |
} | |
public Builder decode404(final boolean decode404) {this.feignClientFactoryBean.setDecode404(decode404); | |
return this; | |
} | |
public Builder fallback(final Class<T> fallback) {FeignClientsRegistrar.validateFallback(fallback); | |
this.feignClientFactoryBean.setFallback(fallback); | |
return this; | |
} | |
public Builder fallbackFactory(final Class<T> fallbackFactory) {FeignClientsRegistrar.validateFallbackFactory(fallbackFactory); | |
this.feignClientFactoryBean.setFallbackFactory(fallbackFactory); | |
return this; | |
} | |
/** | |
* @param <T> the target type of the Feign client to be created | |
* @return the created Feign client | |
*/ | |
public <T> T build() {return this.feignClientFactoryBean.getTarget(); | |
} | |
} | |
} |
- FeignClientBuilder 提供了 forType 静态方法用于创建 Builder;Builder 的构造器创建了 FeignClientFactoryBean,其 build 方法使用 FeignClientFactoryBean 的 getTarget() 来创建目标 feign client
实例
public class FeignClientBuilderTests { | |
@Rule | |
public ExpectedException thrown = ExpectedException.none(); | |
private FeignClientBuilder feignClientBuilder; | |
private ApplicationContext applicationContext; | |
private static Object getDefaultValueFromFeignClientAnnotation(final String methodName) {final Method method = ReflectionUtils.findMethod(FeignClient.class, methodName); | |
return method.getDefaultValue();} | |
private static void assertFactoryBeanField(final FeignClientBuilder.Builder builder, | |
final String fieldName, final Object expectedValue) { | |
final Field factoryBeanField = ReflectionUtils | |
.findField(FeignClientBuilder.Builder.class, "feignClientFactoryBean"); | |
ReflectionUtils.makeAccessible(factoryBeanField); | |
final FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) ReflectionUtils | |
.getField(factoryBeanField, builder); | |
final Field field = ReflectionUtils.findField(FeignClientFactoryBean.class, | |
fieldName); | |
ReflectionUtils.makeAccessible(field); | |
final Object value = ReflectionUtils.getField(field, factoryBean); | |
assertThat(value).as("Expected value for the field'" + fieldName + "':") | |
.isEqualTo(expectedValue); | |
} | |
@Before | |
public void setUp() {this.applicationContext = Mockito.mock(ApplicationContext.class); | |
this.feignClientBuilder = new FeignClientBuilder(this.applicationContext); | |
} | |
@Test | |
public void safetyCheckForNewFieldsOnTheFeignClientAnnotation() {final List<String> methodNames = new ArrayList(); | |
for (final Method method : FeignClient.class.getMethods()) {methodNames.add(method.getName()); | |
} | |
methodNames.removeAll( | |
Arrays.asList("annotationType", "value", "serviceId", "qualifier", | |
"configuration", "primary", "equals", "hashCode", "toString")); | |
Collections.sort(methodNames); | |
// If this safety check fails the Builder has to be updated. | |
// (1) Either a field was removed from the FeignClient annotation and so it has to | |
// be removed | |
// on this builder class. | |
// (2) Or a new field was added and the builder class has to be extended with this | |
// new field. | |
assertThat(methodNames).containsExactly("contextId", "decode404", "fallback", | |
"fallbackFactory", "name", "path", "url"); | |
} | |
@Test | |
public void forType_preinitializedBuilder() { | |
// when: | |
final FeignClientBuilder.Builder builder = this.feignClientBuilder | |
.forType(FeignClientBuilderTests.class, "TestClient"); | |
// then: | |
assertFactoryBeanField(builder, "applicationContext", this.applicationContext); | |
assertFactoryBeanField(builder, "type", FeignClientBuilderTests.class); | |
assertFactoryBeanField(builder, "name", "TestClient"); | |
assertFactoryBeanField(builder, "contextId", "TestClient"); | |
// and: | |
assertFactoryBeanField(builder, "url", | |
getDefaultValueFromFeignClientAnnotation("url")); | |
assertFactoryBeanField(builder, "path", | |
getDefaultValueFromFeignClientAnnotation("path")); | |
assertFactoryBeanField(builder, "decode404", | |
getDefaultValueFromFeignClientAnnotation("decode404")); | |
assertFactoryBeanField(builder, "fallback", | |
getDefaultValueFromFeignClientAnnotation("fallback")); | |
assertFactoryBeanField(builder, "fallbackFactory", | |
getDefaultValueFromFeignClientAnnotation("fallbackFactory")); | |
} | |
@Test | |
public void forType_allFieldsSetOnBuilder() { | |
// when: | |
final FeignClientBuilder.Builder builder = this.feignClientBuilder | |
.forType(FeignClientBuilderTests.class, "TestClient").decode404(true) | |
.fallback(Object.class).fallbackFactory(Object.class).path("Path/") | |
.url("Url/"); | |
// then: | |
assertFactoryBeanField(builder, "applicationContext", this.applicationContext); | |
assertFactoryBeanField(builder, "type", FeignClientBuilderTests.class); | |
assertFactoryBeanField(builder, "name", "TestClient"); | |
// and: | |
assertFactoryBeanField(builder, "url", "http://Url/"); | |
assertFactoryBeanField(builder, "path", "/Path"); | |
assertFactoryBeanField(builder, "decode404", true); | |
assertFactoryBeanField(builder, "fallback", Object.class); | |
assertFactoryBeanField(builder, "fallbackFactory", Object.class); | |
} | |
@Test | |
public void forType_build() { | |
// given: | |
Mockito.when(this.applicationContext.getBean(FeignContext.class)) | |
.thenThrow(new ClosedFileSystemException()); // throw an unusual exception | |
// in the | |
// FeignClientFactoryBean | |
final FeignClientBuilder.Builder builder = this.feignClientBuilder | |
.forType(TestClient.class, "TestClient"); | |
// expect: 'the build will fail right after calling build() with the mocked | |
// unusual exception' | |
this.thrown.expect(Matchers.isA(ClosedFileSystemException.class)); | |
builder.build();} | |
} |
- FeignClientBuilderTests 验证了 safetyCheckForNewFieldsOnTheFeignClientAnnotation、forType_preinitializedBuilder、forType_allFieldsSetOnBuilder、forType_build
小结
FeignClientBuilder 提供了 forType 静态方法用于创建 Builder;Builder 的构造器创建了 FeignClientFactoryBean,其 build 方法使用 FeignClientFactoryBean 的 getTarget() 来创建目标 feign client
doc
- FeignClientBuilder
正文完