Feign的接口和Mybatis的接口相似,都是通過FactoryBean實作了spring整合的,那麼掃包的就不再分析了,直接看FactoryBean–FeignClientFactoryBean
@Override
public Object getObject() throws Exception {
return getTarget();
}
<T> T getTarget() {
FeignContext context = this.applicationContext.getBean(FeignContext.class);
// 配置Fegin
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();
}
if (client instanceof FeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
client = ((FeignBlockingLoadBalancerClient) 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));
}
本方法才是精髓,用于配置Feign;這裡有一個特殊的地方,就是每個Feign用戶端是多例的,這樣就可以實作定制用戶端。那麼怎麼區配置設定置呢?這就用到了FeignContext ,每個用戶端,有各自的配置容器。這方面的就不展開了。自己分析吧。
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);
// 從容器中找到類型,配置Feign
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class));
// @formatter:
//配置Feign
configureFeign(context, builder);
return builder;
}
開發者可以通過配置檔案,定制每個用戶端的每個元件。格式是:
feign.client.用戶端的名稱,即通路服務的名稱.FeignClientConfiguration中的屬性。例如:
feign.client.springcloud-demo-provier.contract=com.test.MyContract
protected void configureFeign(FeignContext context, Feign.Builder builder) {
FeignClientProperties properties = this.applicationContext
.getBean(FeignClientProperties.class);
if (properties != null) {
if (properties.isDefaultToProperties()) {
configureUsingConfiguration(context, builder);
configureUsingProperties(
properties.getConfig().get(properties.getDefaultConfig()),
builder);
configureUsingProperties(properties.getConfig().get(this.contextId),
builder);
}
else {
configureUsingProperties(
properties.getConfig().get(properties.getDefaultConfig()),
builder);
configureUsingProperties(properties.getConfig().get(this.contextId),
builder);
configureUsingConfiguration(context, builder);
}
}
else {
configureUsingConfiguration(context, builder);
}
}
定制服務有兩種方法,一種是配置檔案,第二種是通過注解FeignClient中的configuration屬性,配置自己的。例如:FeignClientsConfiguration,把自己的元件,以配置的形式編寫。
建立執行個體
在getObejct()方法的最後是建立執行個體的邏輯。其實調用的是builder中的target方法。
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
feign.Feign.Builder#target(feign.Target)
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
public Feign build() {
//方法處理器
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy);
// 這個類就是用來解析接口類的,之後轉化為方法處理器
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
public <T> T newInstance(Target<T> target) {
// 這塊已經給分析了一大半了,其實就是将接口類解析出來,之後建立方法處理對象。
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
// 得到方法和方法處理器的映射。
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if (Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
InvocationHandler handler = factory.create(target, methodToHandler);
// 動态代理建立對象。
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
InvocationHandler 很簡單,有個方法和方法處理器的映射,家用那個方法,直接找對應的方法處理器。
public Object invoke(Object[] argv) throws Throwable {
// 這裡是使用真正的值替換占位符;得到RequestTemplate
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
// client的發送邏輯了
return executeAndDecode(template, options);
} catch (RetryableException e) {
try {
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
替換占位符
public RequestTemplate create(Object[] argv) {
//克隆對象
RequestTemplate mutable = RequestTemplate.from(metadata.template());
if (metadata.urlIndex() != null) {
int urlIndex = metadata.urlIndex();
checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex);
mutable.target(String.valueOf(argv[urlIndex]));
}
Map<String, Object> varBuilder = new LinkedHashMap<String, Object>();
// 得到占位符和真正值的對應關系
for (Entry<Integer, Collection<String>> entry : metadata.indexToName().entrySet()) {
int i = entry.getKey();
Object value = argv[entry.getKey()];
if (value != null) { // Null values are skipped.
if (indexToExpander.containsKey(i)) {
value = expandElements(indexToExpander.get(i), value);
}
for (String name : entry.getValue()) {
varBuilder.put(name, value);
}
}
}
// 替換占位符,這裡有不同了,子類的實作同,涉及到解析請求體,請求參數的不同
RequestTemplate template = resolve(argv, mutable, varBuilder);
if (metadata.queryMapIndex() != null) {
// add query map parameters after initial resolve so that they take
// precedence over any predefined values
Object value = argv[metadata.queryMapIndex()];
Map<String, Object> queryMap = toQueryMap(value);
template = addQueryMapQueryParameters(queryMap, template);
}
if (metadata.headerMapIndex() != null) {
template =
addHeaderMapHeaders((Map<String, Object>) argv[metadata.headerMapIndex()], template);
}
return template;
}