乐趣区

聊聊spring-cloud的FeignClientFactoryBean

本文主要研究一下 spring cloud 的 FeignClientFactoryBean

FeignClientFactoryBean

spring-cloud-openfeign-core-2.2.0.M1-sources.jar!/org/springframework/cloud/openfeign/FeignClientFactoryBean.java

class FeignClientFactoryBean
        implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {

    /***********************************
     * WARNING! Nothing in this class should be @Autowired. It causes NPEs because of some
     * lifecycle race condition.
     ***********************************/

    private Class<?> type;

    private String name;

    private String url;

    private String contextId;

    private String path;

    private boolean decode404;

    private ApplicationContext applicationContext;

    private Class<?> fallback = void.class;

    private Class<?> fallbackFactory = void.class;

    @Override
    public void afterPropertiesSet() throws Exception {Assert.hasText(this.contextId, "Context id must be set");
        Assert.hasText(this.name, "Name must be set");
    }

    @Override
    public Object getObject() throws Exception {return getTarget();
    }

    @Override
    public Class<?> getObjectType() {return this.type;}

    @Override
    public boolean isSingleton() {return true;}

    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {this.applicationContext = context;}

    <T> T getTarget() {FeignContext context = this.applicationContext.getBean(FeignContext.class);
        Feign.Builder builder = feign(context);

        if (!StringUtils.hasText(this.url)) {if (!this.name.startsWith("http")) {this.url = "http://" + this.name;}
            else {this.url = this.name;}
            this.url += cleanPath();
            return (T) loadBalance(builder, context,
                    new HardCodedTarget<>(this.type, this.name, this.url));
        }
        if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {this.url = "http://" + this.url;}
        String url = this.url + cleanPath();
        Client client = getOptional(context, Client.class);
        if (client != null) {if (client instanceof LoadBalancerFeignClient) {
                // not load balancing because we have a url,
                // but ribbon is on the classpath, so unwrap
                client = ((LoadBalancerFeignClient) client).getDelegate();}
            builder.client(client);
        }
        Targeter targeter = get(context, Targeter.class);
        return (T) targeter.target(this, builder, context,
                new HardCodedTarget<>(this.type, this.name, url));
    }

    private String cleanPath() {String path = this.path.trim();
        if (StringUtils.hasLength(path)) {if (!path.startsWith("/")) {path = "/" + path;}
            if (path.endsWith("/")) {path = path.substring(0, path.length() - 1);
            }
        }
        return path;
    }

    //......

}
  • FeignClientFactoryBean 实现了 FactoryBean 的 getObject、getObjectType、isSingleton 方法;实现了 InitializingBean 的 afterPropertiesSet 方法;实现了 ApplicationContextAware 的 setApplicationContext 方法
  • getObject 调用的是 getTarget 方法,它从 applicationContext 取出 FeignContext,然后构造 Feign.Builder 并设置了 logger、encoder、decoder、contract,之后通过 configureFeign 根据 FeignClientProperties 来进一步配置 Feign.Builder 的 retryer、errorDecoder、request.Options、requestInterceptors、queryMapEncoder、decode404
  • 初步配置完 Feign.Builder 之后再判断是否需要 loadBalance,如果需要则通过 loadBalance 方法来设置,不需要则在 Client 是 LoadBalancerFeignClient 的时候进行 unwrap

FeignClientProperties

spring-cloud-openfeign-core-2.2.0.M1-sources.jar!/org/springframework/cloud/openfeign/FeignClientProperties.java

@ConfigurationProperties("feign.client")
public class FeignClientProperties {

    private boolean defaultToProperties = true;

    private String defaultConfig = "default";

    private Map<String, FeignClientConfiguration> config = new HashMap<>();

    public boolean isDefaultToProperties() {return this.defaultToProperties;}

    public void setDefaultToProperties(boolean defaultToProperties) {this.defaultToProperties = defaultToProperties;}

    public String getDefaultConfig() {return this.defaultConfig;}

    public void setDefaultConfig(String defaultConfig) {this.defaultConfig = defaultConfig;}

    public Map<String, FeignClientConfiguration> getConfig() {return this.config;}

    public void setConfig(Map<String, FeignClientConfiguration> config) {this.config = config;}

    @Override
    public boolean equals(Object o) {if (this == o) {return true;}
        if (o == null || getClass() != o.getClass()) {return false;}
        FeignClientProperties that = (FeignClientProperties) o;
        return this.defaultToProperties == that.defaultToProperties
                && Objects.equals(this.defaultConfig, that.defaultConfig)
                && Objects.equals(this.config, that.config);
    }

    @Override
    public int hashCode() {return Objects.hash(this.defaultToProperties, this.defaultConfig, this.config);
    }

    /**
     * Feign client configuration.
     */
    public static class FeignClientConfiguration {

        private Logger.Level loggerLevel;

        private Integer connectTimeout;

        private Integer readTimeout;

        private Class<Retryer> retryer;

        private Class<ErrorDecoder> errorDecoder;

        private List<Class<RequestInterceptor>> requestInterceptors;

        private Boolean decode404;

        private Class<Decoder> decoder;

        private Class<Encoder> encoder;

        private Class<Contract> contract;

        public Logger.Level getLoggerLevel() {return this.loggerLevel;}

        public void setLoggerLevel(Logger.Level loggerLevel) {this.loggerLevel = loggerLevel;}

        public Integer getConnectTimeout() {return this.connectTimeout;}

        public void setConnectTimeout(Integer connectTimeout) {this.connectTimeout = connectTimeout;}

        public Integer getReadTimeout() {return this.readTimeout;}

        public void setReadTimeout(Integer readTimeout) {this.readTimeout = readTimeout;}

        public Class<Retryer> getRetryer() {return this.retryer;}

        public void setRetryer(Class<Retryer> retryer) {this.retryer = retryer;}

        public Class<ErrorDecoder> getErrorDecoder() {return this.errorDecoder;}

        public void setErrorDecoder(Class<ErrorDecoder> errorDecoder) {this.errorDecoder = errorDecoder;}

        public List<Class<RequestInterceptor>> getRequestInterceptors() {return this.requestInterceptors;}

        public void setRequestInterceptors(List<Class<RequestInterceptor>> requestInterceptors) {this.requestInterceptors = requestInterceptors;}

        public Boolean getDecode404() {return this.decode404;}

        public void setDecode404(Boolean decode404) {this.decode404 = decode404;}

        public Class<Decoder> getDecoder() {return this.decoder;}

        public void setDecoder(Class<Decoder> decoder) {this.decoder = decoder;}

        public Class<Encoder> getEncoder() {return this.encoder;}

        public void setEncoder(Class<Encoder> encoder) {this.encoder = encoder;}

        public Class<Contract> getContract() {return this.contract;}

        public void setContract(Class<Contract> contract) {this.contract = contract;}

        @Override
        public boolean equals(Object o) {if (this == o) {return true;}
            if (o == null || getClass() != o.getClass()) {return false;}
            FeignClientConfiguration that = (FeignClientConfiguration) o;
            return this.loggerLevel == that.loggerLevel
                    && Objects.equals(this.connectTimeout, that.connectTimeout)
                    && Objects.equals(this.readTimeout, that.readTimeout)
                    && Objects.equals(this.retryer, that.retryer)
                    && Objects.equals(this.errorDecoder, that.errorDecoder)
                    && Objects.equals(this.requestInterceptors, that.requestInterceptors)
                    && Objects.equals(this.decode404, that.decode404)
                    && Objects.equals(this.encoder, that.encoder)
                    && Objects.equals(this.decoder, that.decoder)
                    && Objects.equals(this.contract, that.contract);
        }

        @Override
        public int hashCode() {
            return Objects.hash(this.loggerLevel, this.connectTimeout, this.readTimeout,
                    this.retryer, this.errorDecoder, this.requestInterceptors,
                    this.decode404, this.encoder, this.decoder, this.contract);
        }

    }

}
  • FeignClientProperties 有个 Map 结构的 config,key 是 feign client 的名称,默认是 default,value 是 FeignClientConfiguration;FeignClientConfiguration 包含了 loggerLevel、connectTimeout、readTimeout、retryer、errorDecoder、requestInterceptors、decode404、decoder、encoder、contract 属性

小结

  • FeignClientFactoryBean 实现了 FactoryBean 的 getObject、getObjectType、isSingleton 方法;实现了 InitializingBean 的 afterPropertiesSet 方法;实现了 ApplicationContextAware 的 setApplicationContext 方法
  • getObject 调用的是 getTarget 方法,它从 applicationContext 取出 FeignContext,然后构造 Feign.Builder 并设置了 logger、encoder、decoder、contract,之后通过 configureFeign 根据 FeignClientProperties 来进一步配置 Feign.Builder 的 retryer、errorDecoder、request.Options、requestInterceptors、queryMapEncoder、decode404
  • 初步配置完 Feign.Builder 之后再判断是否需要 loadBalance,如果需要则通过 loadBalance 方法来设置,不需要则在 Client 是 LoadBalancerFeignClient 的时候进行 unwrap

doc

  • FeignClientFactoryBean
退出移动版