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(MethodMetadata data, Annotation methodAnnotation, Method method) { 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(MethodMetadata data, Annotation[] annotations, int paramIndex) { 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(MethodMetadata data, Annotation methodAnnotation, Method method) { 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(MethodMetadata data, Annotation[] annotations, int paramIndex) { 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(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { registerFeignClients(metadata, registry); } public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 掃描配置註解中配置範圍內的 @FeignClient for (String basePackage : basePackages) { // 注入IOC 容器 registerClientConfiguration(registry, name, attributes.get("configuration")); } } //feignclient <--> bean 構造 private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { 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()segmentfault
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(Target target, Map<Method, MethodHandler> dispatch) { 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
等組件源碼圖文解析。api