OpenFeign其實還是負責通信的,在使用過程中,讓開發者可以面向接口開發的方式進行遠程調用,那么:
- OpenFeign的注解是如何被掃描解析的呢?
- 注解標注的接口注入到IoC中的實例是什么?
- OpenFeign的上下文是如何構建的呢?
- 遠程通信是如何實現的呢?與RestTemplate有關系么?
帶著以上的問題,準備開始Feign的源碼,同樣的 在Spring的生態下,切入點還是那幾個,
EnableFeignClients
和FeignAutoConfiguration
,其實 如果看一眼spring-cloud-openfeign-core
里面的spring.factories
就發現 這里面其實有好多AutoConfiguration。
OpenFeign初始化
@EnableFeignClients
這個注解
/** * Scans for interfaces that declare they are feign clients (via * {@link org.springframework.cloud.openfeign.FeignClient} <code>@FeignClient</code>). * Configures component scanning directives for use with * {@link org.springframework.context.annotation.Configuration} * <code>@Configuration</code> classes. * 掃描所有標注了@FeignClient的接口 將其配置成配置類 */ @Import(FeignClientsRegistrar.class) public @interface EnableFeignClients { .... }
這時候 關注點就應該在
FeignClientsRegistrar
FeignClientsRegistrar
雖然沒有在任何的配置中看到 實例化它,但是它實例化的還是很早的,在 org.springframework.context.annotation.ConfigurationClassParser#processImports
registerBeanDefinitions
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
....
// 重點關注這里 ImportBeanDefinitionRegistrar#registerBeanDefinitions
// 兩個參數分別是StandardAnnotationMetadata, DefaultListableBeanFactory
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 如果注解中的defaultConfiguration不為空 則將其注入到IoC容器
registerDefaultConfiguration(metadata, registry);
// 這里是注入FeignClient的地方
registerFeignClients(metadata, registry);
}
....
}
registerFeignClients
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
....
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
// 獲取 @EnableFeignClients 所有的配置信息
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
// 獲取 @EnableFeignClients.clients 的值
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
// 這個集合必然不為空 分別取了value、basePackages、basePackageClasses
// 如果那三個為空 則將標注了@EnableFeignClients 的類所在的路徑加入到這個集合
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
} else {
for (Class<?> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
// 遍歷 并生成代理類 并注入到IoC容器
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());
// @FeignClient的value值
String name = getClientName(attributes);
registerClientConfiguration(registry, name, attributes.get("configuration"));
// 注入標注了@FeignClient的接口
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
....
}
registerFeignClient
這里面沒啥可說的 就是將被FeignClient標注的接口注入到IoC容器 注入的是
FeignClientFactoryBean
目前這個階段是初始化,實例化的時候會調用
FeignClientFactoryBean.getObject()
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
....
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
// 校驗了 fallback和fallbackFactory
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
// FeignClient并無contextId的配置 這里取的還是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);
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
// has a default, won't be null. default is true
boolean primary = (Boolean) attributes.get("primary");
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);
}
....
}
FeignClientFactoryBean#getTarget
首先要清楚
FeignClientFactoryBean
的實例里面在上面的步驟已經將它里面的值做了初始化。而在doGetBean時 會調用getObject方法 ,這里如果不清楚,可以看案例關于FactoryBean
其次就是理解
getTarget
做了哪些事情:
- 生成遠程代理
- 解析接口模板,例如@GetMapping等
- 處理負載均衡 集成Ribbon
- 包裝熔斷 繼承Hystrix
<T> T getTarget() {
// 這個就是feign的上下文 與spring 容器的上下文隔離開了
FeignContext context = applicationContext.getBean(FeignContext.class);
// 構建一個Feign的Builder
Feign.Builder builder = feign(context);
// 這里判斷的是 @FeignClient 的url是否做了配置
if (!StringUtils.hasText(url)) {
if (!name.startsWith("http")) {
url = "http://" + name;
}
else {
url = name;
}
url += cleanPath();
// 這里做負載均衡
return (T) loadBalance(builder, context,
new HardCodedTarget<>(type, name, url));
}
if (StringUtils.hasText(url) && !url.startsWith("http")) {
url = "http://" + url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient) client).getDelegate();
}
if (client instanceof FeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(type, name, url));
}
FeignClientFactoryBean#feign
只是構建了Feign.Builder,其實就是將一些參數記錄。
protected Feign.Builder feign(FeignContext context) {
// get方法 = context.getInstance(contextId, type)
// 其中 context就是第一個參數 type是第二個參數
// contextId是FeignClientsRegistrar.registerFeignClient方法里面設置的
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
// 記錄日志的 this.logger != null ? this.logger : new Slf4jLogger(type)
Logger logger = loggerFactory.create(type);
// @formatter:off
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class));
// @formatter:on
// 配置相關的內容 logger encoder decoder...
configureFeign(context, builder);
return builder;
}
FeignClientFactoryBean#loadBalance
名字雖然叫loadBalance,但是這里并沒有做負載均衡,第一行代碼
Client client = getOptional(context, Client.class);
,是從IoC容器中獲取Client
的Bean,這時觸發了IoC容器對該Bean的實例化,代碼如下:@Configuration(proxyBeanMethods = false) class DefaultFeignLoadBalancedConfiguration { @Bean @ConditionalOnMissingBean public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory) { return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory, clientFactory); } }
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
// client = org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient
Client client = getOptional(context, Client.class);
if (client != null) {
// 這里構造的client
builder.client(client);
// targeter = org.springframework.cloud.openfeign.HystrixTargeter
// 即使在項目中沒有引入hystrix 也是這個類
Targeter targeter = get(context, Targeter.class);
// 這里面做動態代理的構建 與 模板方法的解析
return targeter.target(this, builder, context, target);
}
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
HystrixTargeter#target
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget<T> target) {
// 沒有引入hystrix 時 feign = Feign$Builder 所以就走到了這里
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
// 這里等于 build().newInstance(target),build構建了`ReflectiveFeign`
// 然后這個newInstance是feign.ReflectiveFeign#newInstance
return feign.target(target);
}
feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName()
: factory.getContextId();
SetterFactory setterFactory = getOptional(name, context, SetterFactory.class);
if (setterFactory != null) {
builder.setterFactory(setterFactory);
}
Class<?> fallback = factory.getFallback();
if (fallback != void.class) {
return targetWithFallback(name, context, target, builder, fallback);
}
Class<?> fallbackFactory = factory.getFallbackFactory();
if (fallbackFactory != void.class) {
return targetWithFallbackFactory(name, context, target, builder,
fallbackFactory);
}
return feign.target(target);
}
這里的代碼跳轉是這樣的,return feign.target(target);
點進去是feign.Feign.Builder#target(feign.Target<T>)
,而方法的內容只有return build().newInstance(target);
,而build方法 除了實例化了幾個類,同時將Builder的一些參數做了傳遞,
這里做了一件事就是把client傳過去了。
最后返回了ReflectiveFeign
,所以直接看feign.ReflectiveFeign#newInstance
吧
ReflectiveFeign#newInstance
這里就是通過JDK的動態代理 代理所有的接口了
public <T> T newInstance(Target<T> target) {
// value 是 feign.SynchronousMethodHandler 這一步是模板解析
// 這一步初始化了 SynchronousMethodHandler里面的client
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)));
}
}
// create方法等于new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
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;
}
OpenFeign遠程調用
從初始化的流程來看
feign.MethodHandler
是feign.SynchronousMethodHandler
,而java.lang.reflect.InvocationHandler
是feign.ReflectiveFeign.FeignInvocationHandler
,然后在調用方法時 首先要進入的就是feign.ReflectiveFeign.FeignInvocationHandler#invoke
ReflectiveFeign.FeignInvocationHandler#invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 這里時過濾掉Object相關的方法
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();
}
// dispatch是Map<Method, MethodHandler> (value是SynchronousMethodHandler)
return dispatch.get(method).invoke(args);
}
SynchronousMethodHandler#executeAndDecode
調用鏈為:
feign.SynchronousMethodHandler#invoke
到feign.SynchronousMethodHandler#executeAndDecode
。
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
// 重點在這里 client=org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient
// 在不做特殊配置的情況下,LoadBalancerFeignClient.delegate=feign.Client$Default
// 用的還是HttpURLConnection。
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 12
response = response.toBuilder()
.request(request)
.requestTemplate(template)
.build();
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
throw errorExecuting(request, e);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
if (decoder != null)
return decoder.decode(response, metadata.returnType());
CompletableFuture<Object> resultFuture = new CompletableFuture<>();
asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
metadata.returnType(),
elapsedTime);
try {
if (!resultFuture.isDone())
throw new IllegalStateException("Response handling not done");
return resultFuture.join();
} catch (CompletionException e) {
Throwable cause = e.getCause();
if (cause != null)
throw cause;
throw e;
}
}
LoadBalancerFeignClient#execute
到這里能看到一些負載均衡的字眼了 但是追進去會看到好多RxJava的代碼,這種反應式代碼 只能靠猜。
@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);
// FeignLoadBalancer.executeWithLoadBalancer 其實是父類AbstractLoadBalancerAwareClient
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
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 {
// 最后回調到這里
// org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#execute
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
}).toBlocking()
.single(); // 執行之后可以走到com.netflix.loadbalancer.BaseLoadBalancer#chooseServer
} catch (Exception e) {
Throwable t = e.getCause();
if (t instanceof ClientException) {
throw (ClientException) t;
} else {
throw new ClientException(e);
}
}
}
總結
這里針對開篇提出的問題做一個總結
- OpenFeign的注解是如何被掃描解析的呢?
- 注解標注的接口注入到IoC中的實例是什么?
- OpenFeign的上下文是如何構建的呢?
- 遠程通信是如何實現的呢?與RestTemplate有關系么?
OpenFeign的注解是如何被掃描解析的呢?
通過實現接口ImportBeanDefinitionRegistrar
然后注入實例到IoC容器的。
注解標注的接口注入到IoC中的實例是什么?
就是一個JDK的動態代理java.lang.reflect.Proxy
,其中java.lang.reflect.Proxy#h
的值是ReflectiveFeign$FeignInvocationHandler
,如下圖所示:
OpenFeign的上下文是如何構建的呢?
public class FeignContext extends NamedContextFactory<FeignClientSpecification>
遠程通信是如何實現的呢?與RestTemplate有關系么?
其實就是這個類org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient
,這里用到了委派模式,delegate再默認情況下是feign.Client$Default
,里面的通信方式是java.net.HttpURLConnection
,可以切換到HttpClient
或者OkHttp3
RestTemplate默認也是基于java.net.HttpURLConnection
封裝的一個工具類,Feign中沒有直接使用RestTemplate,他們應該屬于平級關系,不存在誰使用了誰。
疑問點
如何整合的Ribbon?
LoadBalancerFeignClient
和單獨看Ribbon的源碼還有聯系么?
有關系 但是不太一樣了,首先說明一點,Feign 一定要引入spring-cloud-starter-netflix-ribbon
,否則在org.springframework.cloud.openfeign.FeignClientFactoryBean#loadBalance
就會報錯。因此,其實分析的Ribbon的源碼過程依舊會走一遍,且Feign比Ribbon 早配置。
為啥說不太一樣了呢?因為用了Feign就不需要@LoadBalance
和RestTemplate
了,所以請求的時候不會再被LoadBalancerInterceptor
攔截。后面負載均衡那里還會走到Ribbon的com.netflix.loadbalancer.BaseLoadBalancer#chooseServer
。
如何切換請求方式
org.springframework.cloud.openfeign.ribbon.OkHttpFeignLoadBalancedConfiguration
org.springframework.cloud.openfeign.ribbon.HttpClientFeignLoadBalancedConfiguration
org.springframework.cloud.openfeign.loadbalancer.DefaultFeignLoadBalancerConfiguration
Spring的知識點
ImportBeanDefinitionRegistrar
public class TestImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClassName(Hello.class.getName());
registry.registerBeanDefinition("hello", beanDefinition);
}
}
class Hello {
// 省略 getter/setter/toString()
private String name;
}
// 非Spring Boot的環境下,此注解必須有 不然IoC容器中無Hello
@Import(TestImportBeanDefinitionRegistrar.class)
public class Bootstrap {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Bootstrap.class);
System.out.println(context.getBean(Hello.class));
}
}
關于FactoryBean
本案例參考
FeignClientFactoryBean
,將一個接口做出一個代理對象 同時注入到Spring IoC容器中去。
public class TestFactoryBean implements FactoryBean<IHello> {
@Override
public IHello getObject() {
return (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(), new Class<?>[]{IHello.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName());
return "hello FactoryBean";
}
});
}
@Override
public Class<?> getObjectType() {
return IHello.class;
}
}
interface IHello {
String sayS();
Integer sayI();
}
public class TestImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
TestFactoryBean factoryBean = new TestFactoryBean();
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(IHello.class, () -> factoryBean.getObject()).getBeanDefinition();
registry.registerBeanDefinition("hello", beanDefinition);
}
}
@Import(TestImportBeanDefinitionRegistrar.class)
public class Bootstrap {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Bootstrap.class);
IHello bean = context.getBean(IHello.class);
System.out.println(bean);
System.out.println(bean.sayS());
// 這里會報錯 因為方法的返回值是int 但是Proxy里面粗暴返回的String
System.out.println(bean.sayI());
}
}