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