深入理解Feign之原始碼解析
什麼是Feign
Feign是受到Retrofit,JAXRS-2.0和WebSocket的影響,它是一個jav的到http客戶端繫結的開源專案。 Feign的主要目標是將Java Http 客戶端變得簡單。Feign的原始碼地址:https://github.com/OpenFeign/feign
Feign的工作原理
feign是一個偽客戶端,即它不做任何的請求處理。Feign通過處理註解生成request,從而實現簡化HTTP API開發的目的,即開發人員可以使用註解的方式定製request api模板,在傳送http request請求之前,feign通過處理註解的方式替換掉request模板中的引數,這種實現方式顯得更為直接、可理解。
通過包掃描注入FeignClient的bean,該原始碼在FeignClientsRegistrar類:
首先在啟動配置上檢查是否有@EnableFeignClients註解,如果有該註解,則開啟包掃描,掃描被@FeignClient註解介面。程式碼如下:
private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { Map<String, Object> defaultAttrs = metadata .getAnnotationAttributes(EnableFeignClients.class.getName(), true); if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) { String name; if (metadata.hasEnclosingClass()) { name = "default." + metadata.getEnclosingClassName(); } else { name = "default." + metadata.getClassName(); } registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration")); } }
- 過包掃描,當類有@FeignClient註解,將註解的資訊取出,連同類名一起取出,賦給BeanDefinitionBuilder,然後根據BeanDefinitionBuilder得到beanDefinition,最後beanDefinition式注入到ioc容器中,原始碼如下:
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { ClassPathScanningCandidateComponentProvider scanner = getScanner(); scanner.setResourceLoader(this.resourceLoader); Set<String> basePackages; Map<String, Object> attrs = metadata .getAnnotationAttributes(EnableFeignClients.class.getName()); AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter( FeignClient.class); final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients"); if (clients == null || clients.length == 0) { scanner.addIncludeFilter(annotationTypeFilter); basePackages = getBasePackages(metadata); } else { final Set<String> clientClasses = new HashSet<>(); basePackages = new HashSet<>(); for (Class<?> clazz : clients) { basePackages.add(ClassUtils.getPackageName(clazz)); clientClasses.add(clazz.getCanonicalName()); } AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() { @Override protected boolean match(ClassMetadata metadata) { String cleaned = metadata.getClassName().replaceAll("\\$", "."); return clientClasses.contains(cleaned); } }; scanner.addIncludeFilter( new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter))); } for (String basePackage : basePackages) { Set<BeanDefinition> candidateComponents = scanner .findCandidateComponents(basePackage); for (BeanDefinition candidateComponent : candidateComponents) { if (candidateComponent instanceof AnnotatedBeanDefinition) { // verify annotated class is an interface AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent; AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface"); Map<String, Object> attributes = annotationMetadata .getAnnotationAttributes( FeignClient.class.getCanonicalName()); String name = getClientName(attributes); registerClientConfiguration(registry, name, attributes.get("configuration")); registerFeignClient(registry, annotationMetadata, attributes); } } } } 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); 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); String alias = name + "FeignClient"; AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null beanDefinition.setPrimary(primary); String qualifier = getQualifier(attributes); if (StringUtils.hasText(qualifier)) { alias = qualifier; } BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias }); BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); }
注入bean之後,通過jdk的代理,當請求Feign Client的方法時會被攔截,程式碼在ReflectiveFeign類,程式碼如下:
public <T> T newInstance(Target<T> target) {
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;
}
在SynchronousMethodHandler類進行攔截處理,當被FeignClient的方法被攔截會根據引數生成RequestTemplate物件,該物件就是http請求的模板,程式碼如下:
@Override
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template);
} catch (RetryableException e) {
retryer.continueOrPropagate(e);
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
其中有個executeAndDecode()方法,該方法是通RequestTemplate生成Request請求物件,然後根據用client獲取response。
Object executeAndDecode(RequestTemplate template) throws Throwable {
Request request = targetRequest(template);
...//省略程式碼
response = client.execute(request, options);
...//省略程式碼
}
Client元件
其中Client元件是一個非常重要的元件,Feign最終傳送request請求以及接收response響應,都是由Client元件完成的,其中Client的實現類,只要有Client.Default,該類由HttpURLConnnection實現網路請求,另外還支援HttpClient、Okhttp.
首先來看以下在FeignRibbonClient的自動配置類,FeignRibbonClientAutoConfiguration ,主要在工程啟動的時候注入一些bean,其程式碼如下:
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@Configuration
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignRibbonClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null),
cachingFactory, clientFactory);
}
}
在缺失配置feignClient的情況下,會自動注入new Client.Default(),跟蹤Client.Default()原始碼,它使用的網路請求框架為HttpURLConnection,程式碼如下:
@Override
public Response execute(Request request, Options options) throws IOException {
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection).toBuilder().request(request).build();
}
怎麼在feign中使用HttpClient,檢視FeignRibbonClientAutoConfiguration的原始碼
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@Configuration
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignRibbonClientAutoConfiguration {
...//省略程式碼
@Configuration
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
protected static class HttpClientFeignLoadBalancedConfiguration {
@Autowired(required = false)
private HttpClient httpClient;
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
ApacheHttpClient delegate;
if (this.httpClient != null) {
delegate = new ApacheHttpClient(this.httpClient);
}
else {
delegate = new ApacheHttpClient();
}
return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
}
}
...//省略程式碼
}
從程式碼@ConditionalOnClass(ApacheHttpClient.class)註解可知道,只需要在pom檔案加上HttpClient的classpath就行了,另外需要在配置檔案上加上feign.httpclient.enabled為true,從 @ConditionalOnProperty註解可知,這個可以不寫,在預設的情況下就為true.
在pom檔案加上:
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>RELEASE</version>
</dependency>
同理,如果想要feign使用Okhttp,則只需要在pom檔案上加上feign-okhttp的依賴:
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>RELEASE</version>
</dependency>
feign的負載均衡是怎麼樣實現的呢?
通過上述的FeignRibbonClientAutoConfiguration類配置Client的型別(httpurlconnection,okhttp和httpclient)時候,可知最終向容器注入的是LoadBalancerFeignClient,即負載均衡客戶端。現在來看下LoadBalancerFeignClient的程式碼:
@Override
public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = getClientConfig(options, clientName);
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
requestConfig).toResponse();
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
其中有個executeWithLoadBalancer()方法,即通過負載均衡的方式請求。
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, requestConfig);
LoadBalancerCommand<T> command = LoadBalancerCommand.<T>builder()
.withLoadBalancerContext(this)
.withRetryHandler(handler)
.withLoadBalancerURI(request.getUri())
.build();
try {
return command.submit(
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
} catch (Exception e) {
Throwable t = e.getCause();
if (t instanceof ClientException) {
throw (ClientException) t;
} else {
throw new ClientException(e);
}
}
}
其中服務在submit()方法上,點選submit進入具體的方法,這個方法是LoadBalancerCommand的方法:
Observable<T> o =
(server == null ? selectServer() : Observable.just(server))
.concatMap(new Func1<Server, Observable<T>>() {
@Override
// Called for each server being selected
public Observable<T> call(Server server) {
context.setServer(server);
}}
上述程式碼中有個selectServe(),該方法是選擇服務的進行負載均衡的方法,程式碼如下:
private Observable<Server> selectServer() {
return Observable.create(new OnSubscribe<Server>() {
@Override
public void call(Subscriber<? super Server> next) {
try {
Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);
next.onNext(server);
next.onCompleted();
} catch (Exception e) {
next.onError(e);
}
}
});
}
最終負載均衡交給loadBalancerContext來處理,即之前講述的Ribbon,在這裡不再重複。
總結
總到來說,Feign的原始碼實現的過程如下:
- 首先通過@EnableFeignCleints註解開啟FeignCleint
- 根據Feign的規則實現介面,並加@FeignCleint註解
- 程式啟動後,會進行包掃描,掃描所有的@ FeignCleint的註解的類,並將這些資訊注入到ioc容器中。
- 當介面的方法被呼叫,通過jdk的代理,來生成具體的RequesTemplate
- RequesTemplate在生成Request
- Request交給Client去處理,其中Client可以是HttpUrlConnection、HttpClient也可以是Okhttp
- 最後Client被封裝到LoadBalanceClient類,這個類結合類Ribbon做到了負載均衡。
--------------------- 本文來自 方誌朋 的CSDN 部落格 ,全文地址請點選:https://blog.csdn.net/forezp/article/details/73480304