本文主要研究一下spring cloud的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(); } } }
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(); } }
FeignClientBuilder提供了forType静态方法用于创建Builder;Builder的构造器创建了FeignClientFactoryBean,其build方法使用FeignClientFactoryBean的getTarget()来创建目标feign client