本文主要研究一下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