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