OpenFeign
的示例代碼,獲取一個Github
倉庫的全部貢獻者,建立一個issue
。 建議由此開始 DEBUG
調試閱讀源碼interface GitHub {
@RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
@RequestLine("POST /repos/{owner}/{repo}/issues")
void createIssue(Issue issue, @Param("owner") String owner, @Param("repo") String repo);
}
public static class Contributor {
String login;
int contributions;
}
public static class Issue {
String title;
String body;
List<String> assignees;
int milestone;
List<String> labels;
}
public class MyApp {
public static void main(String... args) {
GitHub github = Feign.builder()
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
// Fetch and print a list of the contributors to this library.
List<Contributor> contributors = github.contributors("OpenFeign", "feign");
for (Contributor contributor : contributors) {
System.out.println(contributor.login + " (" + contributor.contributions + ")");
}
}
}
複製代碼
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 class ReflectiveFeign extends Feign {
@Override
public <T> T newInstance(Target<T> target) {
//使用Contract解析接口類上的方法和註解,轉換單獨MethodHandler處理
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
// 使用DK動態代理爲接口生成代理對象,實際業務邏輯交給 InvocationHandler 處理,其實就是調用 MethodHandler
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
return proxy;
}
}
複製代碼
Github.contributors
方法的註解信息呢。 Feign
中提供一個Contract
解析協議,有以下實現。class Default extends Contract.BaseContract {
protected void processAnnotationOnMethod() {
Class<? extends Annotation> annotationType = methodAnnotation.annotationType();
if (annotationType == RequestLine.class) {
//@RequestLine 註解處理邏輯
} else if (annotationType == Body.class) {
//@Body 註解處理邏輯
} else if (annotationType == Headers.class) {
//@Headers 註解處理邏輯
}
}
protected boolean processAnnotationsOnParameter() {
boolean isHttpAnnotation = false;
for (Annotation annotation : annotations) {
Class<? extends Annotation> annotationType = annotation.annotationType();
if (annotationType == Param.class) {
Param paramAnnotation = (Param) annotation;
//@Param 註解處理邏輯
} else if (annotationType == QueryMap.class) {
//@QueryMap 註解處理邏輯
} else if (annotationType == HeaderMap.class) {
//@HeaderMap 註解處理邏輯
}
}
return isHttpAnnotation;
}
}
複製代碼
Annotation | Interface Target |
---|---|
@RequestLine |
Method |
@Param |
Parameter |
@Headers |
Method, Type |
@QueryMap |
Parameter |
@HeaderMap |
Parameter |
@Body |
Method |
spring-cloud-open-feign
的擴展支持SpringMVC
註解,現 feign
版本也已支持public class SpringMvcContract {
// 處理類上的 @RequestMapping
@Override
protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
if (clz.getInterfaces().length == 0) {
RequestMapping classAnnotation = findMergedAnnotation(clz,
RequestMapping.class);
}
}
// 處理 @RequestMapping 註解,固然也支持衍生註解 @GetMapping @PostMapping 等處理
@Override
protected void processAnnotationOnMethod() {
if (!RequestMapping.class.isInstance(methodAnnotation) && !methodAnnotation
.annotationType().isAnnotationPresent(RequestMapping.class)) {
return;
}
RequestMapping methodMapping = findMergedAnnotation(method, RequestMapping.class);
// 獲取請求方法
RequestMethod[] methods = methodMapping.method();
// produce處理
parseProduces(data, method, methodMapping);
// consumes處理
parseConsumes(data, method, methodMapping);
// headers頭處理
parseHeaders(data, method, methodMapping);
data.indexToExpander(new LinkedHashMap<Integer, Param.Expander>());
}
// 處理 請求參數 SpringMVC 原生註解
@Override
protected boolean processAnnotationsOnParameter() {
Param.Expander expander = this.convertingExpanderFactory
.getExpander(typeDescriptor);
if (expander != null) {
data.indexToExpander().put(paramIndex, expander);
}
return isHttpAnnotation;
}
}
複製代碼
MethodHandler
路由如上圖, 根據不一樣的請求方法路由到不一樣的 MethodHandler
實現java
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 {
// 執行請求攔截器
Request request = targetRequest(template);
// 輸出請求報文
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response = client.execute(request, options);
// 根據返回的狀態碼 ,作 Decode 處理
...
return response;
} catch (RetryableException e) {
// 執行重試的相關邏輯
}
}
}
}
複製代碼
// 獲取所有的請求攔截器,一個個執行
Request targetRequest(RequestTemplate template) {
for (RequestInterceptor interceptor : requestInterceptors) {
interceptor.apply(template);
}
return target.apply(template);
}
複製代碼
public enum Level {
/** * 不輸出 */
NONE,
/** * 只記錄輸出Http 方法、URL、狀態碼、執行時間 */
BASIC,
/** * 輸出請求頭 和 Http 方法、URL、狀態碼、執行時間 */
HEADERS,
/** * 輸出請求頭、報文體 和 Http 方法、URL、狀態碼、執行時間 */
FULL
}
複製代碼
java.net
包 實現,沒請求都會建立鏈接實現。能夠配置成 HttpClient
或者 OKHttp
的高性能實現class Default implements Client {
private final SSLSocketFactory sslContextFactory;
private final HostnameVerifier hostnameVerifier;
@Override
public Response execute(Request request, Request.Options options) throws IOException {
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection, request);
}
」
複製代碼
// Spring Cloud 的Client 實現
public class FeignBlockingLoadBalancerClient {
@Override
public Response execute(Request request, Request.Options options) throws IOException {
// 例如請求: http://pig-auth-server/token/info
final URI originalUri = URI.create(request.url());
// 截取到serviceId: pig-auth-server
String serviceId = originalUri.getHost();
// 調用 loadBalancer API 獲取到能夠的服務實例
ServiceInstance instance = loadBalancerClient.choose(serviceId);
// 構建真實的請求URL http://172.17.0.110:8763/token/info
String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri)
.toString();
// 建立請求 並執行
Request newRequest = Request.create(request.httpMethod(), reconstructedUrl,
request.headers(), request.requestBody());
return delegate.execute(newRequest, options);
}
}
複製代碼
class Default implements Encoder {
@Override
public void encode(Object object, Type bodyType, RequestTemplate template) {
if (bodyType == String.class) {
template.body(object.toString());
} else if (bodyType == byte[].class) {
template.body((byte[]) object, null);
} else if (object != null) {
throw new EncodeException(
format("%s is not a type supported by this encoder.", object.getClass()));
}
}
}
複製代碼
public static class Default implements ErrorDecoder {
private final RetryAfterDecoder retryAfterDecoder = new RetryAfterDecoder();
@Override
public Exception decode(String methodKey, Response response) {
FeignException exception = errorStatus(methodKey, response);
Date retryAfter = retryAfterDecoder.apply(firstOrNull(response.headers(), RETRY_AFTER));
if (retryAfter != null) {
return new RetryableException(
response.status(),
exception.getMessage(),
response.request().httpMethod(),
exception,
retryAfter,
response.request());
}
return exception;
}
private <T> T firstOrNull(Map<String, Collection<T>> map, String key) {
if (map.containsKey(key) && !map.get(key).isEmpty()) {
return map.get(key).iterator().next();
}
return null;
}
}
}
複製代碼
注入 自定義的 ErrorDecoder
比較經常使用。git
以上內容爲 OpenFeign
的請求處理流程,下面爲擴展內容 spring-cloud-open-feign
是如何初始化及其運行的呢?github
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
}
複製代碼
@EnableFeignClients
註解,則開啓了 spring-cloud-open-feign
的相關功能。Import(FeignClientsRegistrar.class)
導入FeignClientsRegistrar,掃描 @FeignClient
注入到容器class FeignClientsRegistrar {
@Override
public void registerBeanDefinitions() {
registerFeignClients(metadata, registry);
}
public void registerFeignClients() {
// 掃描配置註解中配置範圍內的 @FeignClient
for (String basePackage : basePackages) {
// 注入IOC 容器
registerClientConfiguration(registry, name,
attributes.get("configuration"));
}
}
//feignclient <--> bean 構造
private void registerFeignClient() {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
String contextId = getContextId(attributes);
definition.addPropertyValue("contextId", contextId);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
...
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
}
複製代碼
public class FeignAutoConfiguration {
// 未引入 feign-hystrix 模塊,則仍是注入 DefaultTargeter
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new DefaultTargeter();
}
}
}
複製代碼
未引入 feign-hystrix
則仍是上文的流程就同最初的流程一致 , 咱們在調用 feignclient.method
會觸發動態代理,執行 MethodHandler 的邏輯spring
HystrixFeign
HystrixFeign
,是否是意味則邏輯變得更了呢最初 0. 入門Demo
Feign.builder()
,就變成了 HystrixFeign.builder()api
public final class HystrixFeign {
public static Builder builder() {
return new Builder();
}
public static final class Builder extends Feign.Builder {
// 注入 HystrixInvocationHandler 的實現
Feign build(final FallbackFactory<?> nullableFallbackFactory) {
super.invocationHandlerFactory(new InvocationHandlerFactory() {
@Override
public InvocationHandler create() {
return new HystrixInvocationHandler(target, dispatch, setterFactory,
nullableFallbackFactory);
}
});
super.contract(new HystrixDelegatingContract(contract));
return super.build();
}
}
}
複製代碼
HystrixInvocationHandler
的實現,使用HystrixCommand 包裝,最終仍是使用methodhandler 去調用最終的接口final class HystrixInvocationHandler implements InvocationHandler {
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
// 使用HystrixCommand 包裝
HystrixCommand<Object> hystrixCommand =
new HystrixCommand<Object>(setterMethodMap.get(method)) {
@Override
protected Object run() throws Exception {
try {
// 調用 methodhandler 處理最終的請求
return HystrixInvocationHandler
.this.dispatch.get(method).invoke(args);
} catch (Exception e) {
throw e;
} catch (Throwable t) {
throw (Error) t;
}
}
};
return hystrixCommand.execute();
}
}
複製代碼
SentinelFeign
like {@link HystrixFeign.Builder}
,"借鑑" HystrixFeign/** * {@link Feign.Builder} like {@link HystrixFeign.Builder}. */
public final class SentinelFeign {
}
複製代碼
SentinelInvocationHandler
的實現,使用Sentinel包裝,最終仍是使用methodhandler 去調用最終的接口public class SentinelInvocationHandler implements InvocationHandler {
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
// 使用sentinel 包裝請求
try {
ContextUtil.enter(resourceName);
entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
result = methodHandler.invoke(args);
}
catch (Throwable ex) {
// fallback 邏輯
}
finally {
ContextUtil.exit();
}
return result;
}
}
複製代碼
歡迎關注我,後邊更新 Ribbon
、Hystrix
、Sentinel
、Nacos
等組件源碼圖文解析。架構
另注: 以上圖片素材 (omnigraffle & 億圖) 能夠在公衆號 JAVA架構日記
獲取app