Feign是簡化Java HTTP客戶端開發(fā)的工具(java-to-httpclient-binder)宙地,它的靈感來自于Retrofit田炭、JAXRS-2.0和WebSocket未妹。Feign的初衷是降低統(tǒng)一綁定Denominator到HTTP API的復(fù)雜度舀奶。
下來我們通過簡單用例來分析工作核心原理以及流程
interface GitHub {
@RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
}
static class Contributor {
String login;
int contributions;
}
public static void main(String... args) {
GitHub github = Feign.builder()
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
// 獲取貢獻者列表蔚叨,并打印其登錄名以及貢獻次數(shù)
List<Contributor> contributors = github.contributors("netflix", "feign");
for (Contributor contributor : contributors) {
System.out.println(contributor.login + " (" + contributor.contributions + ")");
}
}
- ==通過上面樣例我們看到首先定義了一個 GitHub接口敬扛,然后通過Feign.builder().target()創(chuàng)建了一個GitHub接口的實例。
那么Feign.builder().target()這里應(yīng)該就是Feign的核心部分了幔荒,接下來我們跟蹤源碼糊闽,查看Feign都為我們做了什么事情==
- Feign.builder()創(chuàng)建Builder實例對象
//1.創(chuàng)建Builder實例對象
public static Feign.Builder builder() {
//調(diào)用創(chuàng)建Builder構(gòu)建函數(shù)
return new Feign.Builder();
}
//2.Builder構(gòu)建函數(shù)梳玫,初始化組件信息,當(dāng)然我們也可以自定義組件
public Builder() {
//日志級別
this.logLevel = Level.NONE;
//注解解析組件
this.contract = new Default();
//http發(fā)送組件
this.client = new feign.Client.Default((SSLSocketFactory)null, (HostnameVerifier)null);
//重試機制組件
this.retryer = new feign.Retryer.Default();
//日志
this.logger = new NoOpLogger();
//編碼解碼器組件
this.encoder = new feign.codec.Encoder.Default();
this.decoder = new feign.codec.Decoder.Default();
this.errorDecoder = new feign.codec.ErrorDecoder.Default();
this.options = new Options();
//默認(rèn)的反射InvocationHandlerFactory
// Feign 使用的jdk自帶的動態(tài)代理
this.invocationHandlerFactory = new feign.InvocationHandlerFactory.Default();
}
- Builder.target()創(chuàng)建目標(biāo)實例對象
//1. 調(diào)用target傳入目標(biāo)接口以及請求URL
public <T> T target(Class<T> apiType, String url) {
return this.target(new HardCodedTarget(apiType, url));
}
//2.根據(jù)Target包裝對象創(chuàng)建目標(biāo)接口的實例對象
public <T> T target(Target<T> target) {
//通過3構(gòu)建一個Feign對象右犹,然后通過newInstance方法獲取目標(biāo)實例對象
return this.build().newInstance(target);
}
// 3. 構(gòu)建Feign對象
public Feign build() {
//創(chuàng)建方法代理類工廠
Factory synchronousMethodHandlerFactory = new Factory(this.client, this.retryer, this.requestInterceptors, this.logger, this.logLevel, this.decode404);
//
ParseHandlersByName handlersByName = new ParseHandlersByName(this.contract, this.options, this.encoder, this.decoder, this.errorDecoder, synchronousMethodHandlerFactory);
//這里返回真實的Feign對象
return new ReflectiveFeign(handlersByName, this.invocationHandlerFactory);
}
- Feign.newInstance()(通過2我們可以看出實際是通過ReflectiveFeign對象的newInstance方法創(chuàng)建)
//1. 這里是創(chuàng)建目錄接口實例對象的真正地方
// 這里可以看到是使用了jdk自帶的動態(tài)代理實現(xiàn)
//可以很清楚的看到返回的是目標(biāo)接口的代理對象
public <T> T newInstance(Target<T> target) {
Map<String, MethodHandler> nameToHandler = this.targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList();
InvocationHandler handler = this.factory.create(target, methodToHandler);
//這里可以看到是使用了jdk自帶的動態(tài)代理實現(xiàn)的
//那么我們知道jdk動態(tài)代理真正執(zhí)行的是InvocationHandler接口中的invoke方法提澎,我們再跟蹤invoke,看下執(zhí)行目標(biāo)接口方法時具體邏輯念链。
T proxy = Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);
return proxy;
}
//2. 執(zhí)行目標(biāo)接口方法帶來具體實現(xiàn)(FeignInvocationHandler)
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在此我們可以看出目標(biāo)函數(shù)除了equals盼忌,hashCode,toString方法外都會調(diào)用this.dispatch.get(method)).invoke(args)
//dispatch是目標(biāo)函數(shù)代理類集合掂墓,目標(biāo)接口中每個函數(shù)都會對應(yīng)有一個MethodHandler類谦纱,至于怎么得到的有興趣可以查看源碼
if(!"equals".equals(method.getName())) {
return "hashCode".equals(method.getName())?Integer.valueOf(this.hashCode()):("toString".equals(method.getName())?this.toString():((MethodHandler)this.dispatch.get(method)).invoke(args));
} else {
}
}
- 通過3我們看出了目錄接口每個函數(shù)的執(zhí)行其實是執(zhí)行其MethodHandler類的invoke方法那么下來我們看下這里具體邏輯
MethodHandler默認(rèn)實現(xiàn)SynchronousMethodHandler
//1. 接口方法執(zhí)行都會調(diào)用其對應(yīng)的invoke方法
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = this.buildTemplateFromArgs.create(argv);
//重試組件
Retryer retryer = this.retryer.clone();
while(true) {
try {
//執(zhí)行請求并解碼
return this.executeAndDecode(template);
} catch (RetryableException var5) {
retryer.continueOrPropagate(var5);
if(this.logLevel != Level.NONE) {
this.logger.logRetry(this.metadata.configKey(), this.logLevel);
}
}
}
}
//2 構(gòu)建request請求并執(zhí)行和解碼
Object executeAndDecode(RequestTemplate template) throws Throwable {
//1. 獲取request請求
Request request = this.targetRequest(template);
long start = System.nanoTime();
Response response;
try {
//通過http組件發(fā)送請求
response = this.client.execute(request, this.options);
response.toBuilder().request(request).build();
} catch (IOException var15) {
}
//解碼操作調(diào)用解碼組件進行解碼
//這里省略
return var9;
}
//3組裝request請求,這里同時完成了攔截器調(diào)用的邏輯
Request targetRequest(RequestTemplate template) {
//獲取當(dāng)前請求的所有攔截器
Iterator var2 = this.requestInterceptors.iterator();
while(var2.hasNext()) {
RequestInterceptor interceptor = (RequestInterceptor)var2.next();
//依次調(diào)用攔截器進行攔截操作
interceptor.apply(template);
}
//返回Request對象
return this.target.apply(new RequestTemplate(template));
}
通過上面四步我們可以清晰的看出我們通過定義目標(biāo)接口是怎么一步一步的完成了http請求發(fā)送與接收君编。
在項目中我們使用Feign是簡化我們的http操作跨嘉,同時我們理解整個http請求響應(yīng)是怎么通過Feign來完成的。這樣后期不管是定制還是問題定位吃嘿,我們都能快速有效的分析祠乃。
后記
本文記錄了使用Feign后想了解Feign實現(xiàn)原理然后跟蹤源碼的一些流程和心得。