SpringCloud.Honxton 版本 OpenFeign原理
前置說明
好久沒寫博客了, 突然想起自己的springcloud系列文章就講了配置中心,註冊中心,負載均衡和熔斷。那麼今天就來分析一下openfegin 的原理,後續會分析gateway的原理。
我個人的習慣,在使用springcloud 的組件時,會先去用一下原生依賴的使用方法,因爲在springboot的自動裝配下讓使用變得簡單,但使用也更加機械化,導致我們會無法看透到底在用一個什麼東西。 所以我們先單獨分析一下openfeign的使用以及其本身的原理,然後再來分析springcloud如何進行的整合(下篇)。
openfegin是由neflix公司開源出來,再迭代了數個版本之後徹底改名爲openfeign,在被springcloud整合之後也是十分的受大家的歡迎。
如何使用
public interface HelloOpenfeign {
@RequestLine("GET /feign?feign={feign}")
@Headers("Content-Type: application/x-www-form-urlencoded")
String feign(@Param("feign") String feign);
@RequestLine("POST /feignPost")
@Headers("Content-Type: application/x-www-form-urlencoded")
String feignPost(@Param("feign") String feign);
@RequestLine("POST /feignJson")
@Headers("Content-Type: application/json")
String feignForm(@Param("feign") String feign);
public static void main(String[] args) {
HelloOpenfeign client = Feign.builder().target(HelloOpenfeign.class, "http://localhost:12001");
String result = client.feign("YourBatman");
System.out.println(result);
}
}
哎,是不是沒有想到,使用方法竟然如此簡單, 其實就是解析接口所有的方法,根據你配置的註解完成http參數的拼裝,然後給目的地的ip:port就可以了。最後獲取動態代理對象,那麼就像調用本地方法一樣調用http請求了。
openfeign原理
前面其實已經講了內部原理的大致流程, 也就是解析接口中的方法, 然後生成代理對象; 在真正調用時,解析傳遞的參數,然後封裝request,最後進行真正遠程調用, 最後對返回的結果解析封裝成接口方法的返回類。
下面開始結合源碼進行關鍵點的分析。
入口方法就是 feign.Feign.Builder#target(java.lang.Class, java.lang.String)
public static class Builder {
... 省略其他方法
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
public Feign build() {
// 用於創建真正執行的handler 的工廠
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);
// build模式創建的Feign接口的實現類
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
}
然後來看一下ReflectiveFeign
public class ReflectiveFeign extends Feign {
@Override
public <T> T newInstance(Target<T> target) {
// 用 接口方法解析器解析所有的方法, 產生MethodHandler抽象
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;
}
}
然後簡單分析一下參數解析的過程, 主要是 ParseHandlersByName
這個類
public Map<String, MethodHandler> apply(Target key) {
List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
for (MethodMetadata md : metadata) {
BuildTemplateByResolvingArgs buildTemplate;
if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
// 一般的post,有表單數據的生成器
buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
} else if (md.bodyIndex() != null) {
buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
} else {
// 一般的get, 沒有表單數據的生成器
buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder);
}
// 這裏是關鍵的創建 SynchronousMethodHandler
result.put(md.configKey(),
factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
}
return result;
}
看到這其實openfeign
的初始化流程已經很明確, 然後是動態代理的執行過程的分析 具體見FeignInvocationHandler
static class FeignInvocationHandler implements InvocationHandler {
private final Target target;
private final Map<Method, MethodHandler> dispatch;
FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("equals".equals(method.getName())) {
try {
Object otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
// 關鍵的方法
return dispatch.get(method).invoke(args);
}
... 省略多餘代碼
}
final class SynchronousMethodHandler implements MethodHandler {
@Override
public Object invoke(Object[] argv) throws Throwable {
//根據輸入的參數, 生成對應的請求模板
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
// 重試策略
Retryer retryer = this.retryer.clone();
while (true) {
try {
// 真正的執行遠程調用和解析
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;
}
}
}
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
// 這裏是執行攔截器的方法, 然後生成一個真正的請求體
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
// 這裏又是經典的策略模式, 也是springcloud集成負載均衡的關鍵點
// 這裏是進行遠程調用
response = client.execute(request, options);
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
throw errorExecuting(request, e);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
boolean shouldClose = true;
try {
if (logLevel != Logger.Level.NONE) {
response =
logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
}
...
if (response.status() >= 200 && response.status() < 300) {
if (void.class == metadata.returnType()) {
return null;
} else {
Object result = decode(response); // 轉換成爲方法的返回對象
shouldClose = closeAfterDecode;
return result;
}
} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
} else {
throw errorDecoder.decode(metadata.configKey(), response);
}
}
... 省略多餘代碼
}
// 默認的client實現
class Default implements Client {
private final SSLSocketFactory sslContextFactory;
private final HostnameVerifier hostnameVerifier;
@Override
public Response execute(Request request, Options options) throws IOException { // 是最簡單的 jdk提供的http連接器
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection, request);
}
}
基本上feigin的啓動和執行過程都分析到了, 只有一些參數解析的和響應對象解析的沒有分析(這些細節不影響我們宏觀把控feign的流程).
最後給上一副feign 的分層架構設計圖.
總結
整體來說 feign 的使用非常的便捷, 但是不爽的地方在於就我們需要自己調用Feign.builder().target
來生成代理對象.不過好在有spring來幫我們做了一些瑣碎的事情來避免我們針對每個接口都調用target方法, 就像Aop的整合一樣, 省去了我們自己生產動態代理對象.
下一篇我就會講述springcloud下openfeign的使用,整合的原理以及自動配置的原理.