聊聊spring-cloud的FeignClientBuilder

51次阅读

共计 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

正文完
 0