接下來,我們開始分析 OpenFeign 的生命週期,結合 OpenFeign 本身的原始碼。首先是從介面定義建立 OpenFeign 代理開始。我們這裡只關心同步客戶端,因為非同步客戶端目前還在實現中,並且在我們的專案中,非同步響應式的客戶端不用 OpenFeign,而是用的官方的 WebClient
建立 OpenFeign 代理
建立 OpenFeign 代理,主要分為以下幾步:
- 使用 Contract 解析介面的每一個方法,生成每一個方法的後設資料列表:
List<MethodMetadata> metadata
- 根據每一個
MethodMetadata
,生成對應的請求模板工廠RequestTemplate.Factory
,用於生成後面的請求。同時,使用這個模板工廠以及其他配置生成對應的方法處理器MethodHandler
,對於同步的 OpenFeign,MethodHandler
實現為SynchronousMethodHandler
。將介面方法與MethodHandler
一一對應建立對映,結果為Map<Method, MethodHandler> methodToHandler
。對於 Java 8 引入的 interface default 方法,需要用不同MethodHandler
,即DefaultMethodHandler
,因為這種方法不用代理,不用生成對應的 http 呼叫,其實現為直接呼叫對應的 default 方法程式碼。 - 使用 InvocationHandlerFactory 這個工廠,建立
InvocationHandler
用於代理呼叫。 - 呼叫 JDK 動態代理生成類方法使用
InvocationHandler
建立代理類。
建立 OpenFeign 代理,主要基於 JDK 的動態代理實現。我們先舉一個簡單的例子,建立一個 JDK 動態代理,用來類比。
JDK 動態代理
使用 JDK 動態代理,需要如下幾個步驟:
1. 編寫介面以及對應的代理類。我們這裡編寫一個簡單的介面和對應的實現類:
public interface TestService {
void test();
}
public class TestServiceImpl implements TestService {
@Override
public void test() {
System.out.println("TestServiceImpl#test is called");
}
}
2.建立代理類實現java.lang.reflect.InvocationHandler
,並且,在核心方法中,呼叫實際的物件,這裡即我們上面 TestService 的實現類 TestServiceImpl 的物件。
JDK 中有內建的動態代理 API,其核心是 java.lang.reflect.InvocationHandler
。我們先來建立一個簡單的 InvocationHandler
實現類:
public class SimplePrintMethodInvocationHandler implements InvocationHandler {
private final TestService testService;
public SimplePrintMethodInvocationHandler(TestService testService) {
this.testService = testService;
}
@Override
public Object invoke(
//代理物件
Object proxy,
//呼叫的方法
Method method,
//使用的引數
Object[] args)
throws Throwable {
System.out.println("Invoked method: " + method.getName());
//進行實際的呼叫
return method.invoke(testService, args);
}
}
3.建立代理物件,並使用代理物件呼叫。一般通過 Proxy 的靜態方法去建立,例如:
//首先,建立要代理的物件
TestServiceImpl testServiceImpl = new TestServiceImpl();
//然後使用要代理的物件建立對應的 InvocationHandler
SimplePrintMethodInvocationHandler simplePrintMethodInvocationHandler = new SimplePrintMethodInvocationHandler(testServiceImpl);
//建立代理類,因為一個類可能實現多個介面,所以這裡返回的是 Object,使用者根據自己需要強制轉換成要用的介面
Object proxyInstance = Proxy.newProxyInstance(
TestService.class.getClassLoader(),
testServiceImpl.getClass().getInterfaces(),
simplePrintMethodInvocationHandler
);
//強制轉換
TestService proxied = (TestService) proxyInstance;
//使用代理物件進行呼叫
proxied.test();
這樣,我們就使用了 JDK 的內建動態代理機制實現了一個簡單的動態代理。在 OpenFeign 的使用中,和我們的示例有一點區別。首先,我們只需要定義要代理的介面,不用定義實現類。因為所有的 OpenFeign 介面要做的事情其實都是 HTTP 呼叫,其資訊可以自動從介面定義中生成,我們可以使用統一的物件根據介面定義,承載 OpenFeign 介面定義的請求。在 OpenFeign 中,這個等同於實現物件的,就是根據介面生成的 MethodHandler,在同步的 OpenFeign 中,即 feign.SynchronousMethodHandler
。之後,OpenFeign 建立的 InvocationHandler,其實就是將呼叫轉發到對應的 SynchronousMethodHandler
的對應方法。
建立 OpenFeign 代理物件的流程詳解
我們使用前面的例子,來看一下建立代理的流程:
interface GitHub {
/**
* 定義get方法,包括路徑引數,響應返回序列化類
* @param owner
* @param repository
* @return
*/
@RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repository);
/**
* 響應體結構類
*/
class Contributor {
String login;
int contributions;
public Contributor() {
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public int getContributions() {
return contributions;
}
public void setContributions(int contributions) {
this.contributions = contributions;
}
}
}
/**
* 基於 FastJson 的反序列化解碼器
*/
static class FastJsonDecoder implements Decoder {
@Override
public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException {
//讀取 body
byte[] body = response.body().asInputStream().readAllBytes();
return JSON.parseObject(body, type);
}
}
public static void main(String[] args) {
//建立 Feign 代理的 HTTP 呼叫介面實現
GitHub github = Feign.builder()
//指定解碼器為 FastJsonDecoder
.decoder(new FastJsonDecoder())
//指定代理類為 GitHub,基址為 https://api.github.com
.target(GitHub.class, "https://api.github.com");
List<GitHub.Contributor> contributors = github.contributors("OpenFeign", "feign");
}
我們這裡關心的其實就是建立 Feign 代理的 HTTP 呼叫介面實現這一步的內部流程。首先我們來看 Feign 的 Builder 的結構,當我們初始化一個 Feign 的 Builder 也就是呼叫 Feign.builder()
時,會建立如下元件(同時也說明以下元件都是可以配置的,如果一些配置之前沒有提到,則可以):
//請求攔截器列表,預設為空
private final List<RequestInterceptor> requestInterceptors = new ArrayList();
//日誌級別,預設不列印任何日誌
private Level logLevel = Level.NONE;
//負責解析類後設資料的 Contract,預設是支援 OpenFeign 內建註解的預設 Contract
private Contract contract = new Contract.Default();
//承載 HTTP 請求的 Client,預設是基於 Java HttpURLConnection 的 Default Client
private Client client = new feign.Client.Default((SSLSocketFactory)null, (HostnameVerifier)null);
//重試器,預設也是 Default
private Retryer retryer = new feign.Retryer.Default();
//預設的日誌 Logger,預設不記錄任何日誌
private Logger logger = new NoOpLogger();
//編碼器解碼器也是預設的
private Encoder encoder = new feign.codec.Encoder.Default();
private Decoder decoder = new feign.codec.Decoder.Default();
//查詢引數編碼,這個我們一般不會修改
private QueryMapEncoder queryMapEncoder = new FieldQueryMapEncoder();
//錯誤編碼器,預設為 Default
private ErrorDecoder errorDecoder = new feign.codec.ErrorDecoder.Default();
//各種超時的 Options 走的預設配置
private Options options = new Options();
//用來生成 InvocationHandler 的 Factory 也是預設的
private InvocationHandlerFactory invocationHandlerFactory = new feign.InvocationHandlerFactory.Default();
//是否特殊解析 404 錯誤,因為針對 404 我們可能不想丟擲異常,預設是 false
private boolean decode404 = false;
//是否在解碼後立刻關閉 Response,預設為是
private boolean closeAfterDecode = true;
//異常傳播規則,預設是不傳播
private ExceptionPropagationPolicy propagationPolicy = ExceptionPropagationPolicy.NONE;
//是否強制解碼,這個主要為了相容非同步 Feign 引入的配置,我們直接忽略,認為他就是 false 即可
private boolean forceDecoding = false;
private List<Capability> capabilities = new ArrayList();
我們的程式碼中指定了指定解碼器為 FastJsonDecoder,所以 Decoder 就是 FastJsonDecoder 了。最後通過 target(GitHub.class, "https://api.github.com");
指定定代理類為 GitHub,基址為 https://api.github.com,這時候就會生成 Feign 代理類,其步驟是:
public <T> T target(Class<T> apiType, String url) {
//使用代理介面型別,以及基址建立 HardCodedTarget,他的意思其實就是硬編碼的 Target
return target(new HardCodedTarget<T>(apiType, url));
}
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
public Feign build() {
//將所有元件經過所有的 Capability,從這裡我們可以看出,我們可以實現 Capability 介面來在建立 Feign 代理的時候動態修改元件
Client client = Capability.enrich(this.client, capabilities);
Retryer retryer = Capability.enrich(this.retryer, capabilities);
List<RequestInterceptor> requestInterceptors = this.requestInterceptors.stream()
.map(ri -> Capability.enrich(ri, capabilities))
.collect(Collectors.toList());
Logger logger = Capability.enrich(this.logger, capabilities);
Contract contract = Capability.enrich(this.contract, capabilities);
Options options = Capability.enrich(this.options, capabilities);
Encoder encoder = Capability.enrich(this.encoder, capabilities);
Decoder decoder = Capability.enrich(this.decoder, capabilities);
InvocationHandlerFactory invocationHandlerFactory =
Capability.enrich(this.invocationHandlerFactory, capabilities);
QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities);
//建立 SynchronousMethodHandler 的 Factory,用於生成 SynchronousMethodHandler,SynchronousMethodHandler 是實際承載 Feign 代理請求的實現類
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
//通過方法名稱來區分不同介面方法的後設資料解析,用於生成並路由到對應的代理方法
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
//建立 ReflectiveFeign
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
}
建立 ReflectiveFeign 之後,會呼叫其中的 newInstance 方法:
public <T> T newInstance(Target<T> target) {
//使用前面提到的 ParseHandlersByName 解析後設資料並生成所有需要代理的方法的 MethodHandler,我們這裡分析的是同步 Feign,所以是 SynchronousMethodHandler
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
//將方法與對應的 MethodHandler 一一對應
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
//對於 Object 的方法,直接跳過
continue;
} else if (Util.isDefault(method)) {
//如果是 java 8 中介面的預設方法,就使用 DefaultMethodHandler 處理
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
//使用前面 Builder 中的 InvocationHandlerFactory 建立 InvocationHandler
InvocationHandler handler = factory.create(target, methodToHandler);
//使用 InvocationHandler 建立 Proxy
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
//將代理與 DefaultMethodHandler 關聯
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
對於使用前面提到的 ParseHandlersByName 解析後設資料並生成所有需要代理的方法的 MethodHandler 這一步,主要就涉及到了使用 Contract 解析出方法的後設資料,然後將這些後設資料用對應的編碼器繫結用於之後呼叫的編碼:
public Map<String, MethodHandler> apply(Target target) {
// 使用 Contract 解析出所有方法的後設資料
List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());
Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
// 對於每個解析出的方法後設資料
for (MethodMetadata md : metadata) {
BuildTemplateByResolvingArgs buildTemplate;
if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
//有表單的情況
buildTemplate =
new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
} else if (md.bodyIndex() != null) {
//有 body 的情況
buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
} else {
//其他情況
buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target);
}
if (md.isIgnored()) {
result.put(md.configKey(), args -> {
throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
});
} else {
// 使用 SynchronousMethodHandler 的 Factory 生成 SynchronousMethodHandler
result.put(md.configKey(),
factory.create(target, md, buildTemplate, options, decoder, errorDecoder));
}
}
return result;
}
}
預設的 InvocationHandlerFactory 生成的 InvocationHandler 是 ReflectiveFeign.FeignInvocationHandler:
static final class Default implements InvocationHandlerFactory {
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
}
}
其中的內容是:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 對於 equals,hashCode,toString 方法直接呼叫
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();
}
//對於其他方法,呼叫對應的 SynchronousMethodHandler 進行處理
return dispatch.get(method).invoke(args);
}
從這裡我們就可以看出,我們生成的 Proxy,其實就是將請求代理到了 SynchronousMethodHandler 上。
我們這一節詳細介紹了 OpenFeign 建立代理的詳細流程,可以看出,對於同步 Feign 生成的 Proxy,其實就是將介面 HTTP 請求定義的方法請求代理到了 SynchronousMethodHandler 上。下一節我們會詳細分析 SynchronousMethodHandler 做實際 HTTP 呼叫的流程,來搞清楚 Feign 所有元件是如何協調工作的。
微信搜尋“我的程式設計喵”關注公眾號,每日一刷,輕鬆提升技術,斬獲各種offer: