上接:
SpringCloud OpenFeign_钱多多_qdd的博客-CSDN博客
前言
源码图:
一、Bean的动态装载
在spring系列里bean的动态装载是很常见的,感兴趣可以看看我的这篇文章:
Bean的动态装载_钱多多_qdd的博客-CSDN博客_动态加载bean
二、@FeignClient解析
从下面这个类开始切入,这个注解开启了FeignClient的解析过程。
@EnableFeignClients(basePackages = "com.gupaoedu.example.clients")
2.1 FeignClientsRegistrar
- registerDefaultConfiguration 方法内部从 SpringBoot 启动类上检查是否有@EnableFeignClients, 有该注解的话, 则完成 Feign 框架相关的一些配置内容注册
- registerFeignClients 方法内部从 classpath 中, 扫描获得 @FeignClient 修饰的类, 将类的内容解析为 BeanDefinition , 最终通过调用 Spring 框架中的
BeanDefinitionReaderUtils.resgisterBeanDefinition 将解析处理过的 FeignClient BeanDeifinition 添加到 spring 容器中
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//注册@EnableFeignClients中定义defaultConfiguration属性下的类,包装成FeignClientSpecification,注册到Spring容器。
registerDefaultConfiguration(metadata, registry);
//在@FeignClient中有一个属性:configuration,这个属性是表示各个FeignClient自定义的配置类,后面也会通过调用registerClientConfiguration方法来注册成FeignClientSpecification到容器。
//所以,这里可以完全理解在@EnableFeignClients中配置的是做为兜底的配置,在各个@FeignClient配置的就是自定义的情况。
registerFeignClients(metadata, registry);
}
?这里面需要重点分析的就是 registerFeignClients 方法,这个方法主要是扫描类路径下所有的 @FeignClient注解,然后进行动态Bean的注入。它最终会调用 registerFeignClient 方法。
由上图我们可以看到此方法大概有5个步骤:
- 获取@EnableFeignClient的相关属性;
- 获取服务端类所在的路径basePackages(@EnableFeignClient("xx.xx.xx")配置的)。后续会遍历这些路径,并找到@FeignClient注解的类,将相关的类注册;
- 扫描basePackage下的所有类;
- 获取@FeignClient注解的相关属性;
- 注册;
下面我们来着重看第5步注册:
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);
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
// has a default, won't be null
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);
}
在这个方法中,就是去组装BeanDefinition,也就是Bean的定义,然后注册到Spring IOC容器。
我们关注一下,BeanDefinitionBuilder是用来构建一个BeanDefinition的,它是通过 genericBeanDefinition 来构建的,并且传入了一个FeignClientFactoryBean的类,代码如下。 我们可以发现,FeignClient被动态注册成了一个FactoryBean,之前听过tom老师的源码的同学应该知道什么是FactoryBean吧。
Spring Cloud FengnClient实际上是利用Spring的代理工厂来生成代理类,所以在这里地方才会、把所有的FeignClient的BeanDefinition设置为FeignClientFactoryBean类型,而 FeignClientFactoryBean继承自FactoryBean,它是一个工厂Bean。 在Spring中,FactoryBean是一个工厂Bean,用来创建代理Bean。 工厂 Bean 是一种特殊的 Bean, 对于 Bean 的消费者来说, 他逻辑上是感知不到这个 Bean 是普通的 Bean 还是工厂 Bean, 只是按照正常的获取 Bean 方式去调用, 但工厂bean 最后返回的实例不是工厂Bean 本身, 而是执行工厂 Bean 的 getObject 逻辑返回的示例。
public static BeanDefinitionBuilder genericBeanDefinition(Class<?> beanClass) {
BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition());
builder.beanDefinition.setBeanClass(beanClass);
return builder;
}
?????????简单来说,FeignClient标注的这个接口,会通过FeignClientFactoryBean.getObject()这个方法获得一个代理对象。
2.2 FeignClientFactoryBean.getObject?
????????getObject调用的是getTarget方法,它从applicationContext取出FeignContext,FeignContext继承了NamedContextFactory,它是用来来统一维护feign中各个feign客户端相互隔离的上下文。
FeignContext注册到容器是在FeignAutoConfiguration上完成的:
在初始化FeignContext时,会把configurations在容器中放入FeignContext中。configurations的来源就是在前面registerFeignClients方法中将@FeignClient的配置configuration。
- 接着,构建feign.builder,在构建时会向FeignContext获取配置的Encoder,Decoder等各种信息。
- FeignContext在上篇中已经提到会为每个Feign客户端分配了一个容器,它们的父容器就是spring容器配置完Feign.Builder之后,再判断是否需要LoadBalance,如果需要,则通过LoadBalance的方法来设置。
- 实际上他们最终调用的是Target.target()方法。?
2.3 loadBalance
生成具备负载均衡能力的feign客户端,为feign客户端构建起绑定负载均衡客户端Client client = (Client)this.getOptional(context, Client.class);从上下文中获取一个Client,默认是LoadBalancerFeignClient。
——绿色②代码。
它是在FeignRibbonClientAutoConfiguration这个自动装配类中,通过Import实现的:
?绿色①代码:
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
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?");
}
2.4?DefaultTarget.target
?
2.5 ReflectiveFeign.newInstance
????????这个方法是用来创建一个动态代理的方法,在生成动态代理之前,会根据Contract协议(协议解析规则,解析接口类的注解信息,解析成内部的MethodHandler的处理方式。
????????从实现的代码中可以看到熟悉的Proxy.newProxyInstance方法产生代理类。而这里需要对每个定义的接口方法进行特定的处理实现,所以这里会出现一个MethodHandler的概念,就是对应方法级别的InvocationHandler。
三、接口定义的参数解析
根据FeignClient接口的描述解析出对应的请求数据。
3.1 targetToHandlersByName.apply(target)
还是在ReflectiveFeign.newInstance方法里:
?根据Contract协议规则,解析接口类的注解信息,解析成内部表现:
????????targetToHandlersByName.apply(target);会解析接口方法上的注解,从而解析出方法粒度的特定的配置信息,然后生产一个SynchronousMethodHandler 然后需要维护一个<method,MethodHandler>的map,放入InvocationHandler的实现FeignInvocationHandler中。
3.2?SpringMvcContract
????????当前Spring Cloud 微服务解决方案中,为了降低学习成本,采用了Spring MVC的部分注解来完成 请求协议解析,也就是说 ,写客户端请求接口和像写服务端代码一样:客户端和服务端可以通过SDK的方式进行约定,客户端只需要引入服务端发布的SDK API,就可以使用面向接口的编码方式对接服务。
扩展:Feign详解4-Contract 源码_zhangyingchengqi的博客-CSDN博客
四、OpenFeign调用过程
????????在前面的分析中,我们知道OpenFeign最终返回的是一个#ReflectiveFeign.FeignInvocationHandler的对象。
????????那么当客户端发起请求时,会进入到FeignInvocationHandler.invoke方法中,这个大家都知道,它是一个动态代理的实现。
而接着,在invoke方法中,会调用 this.dispatch.get(method)).invoke(args) 。 this.dispatch.get(method) 会返回一个SynchronousMethodHandler,进行拦截处理。
这个方法会根据参数生成完成的RequestTemplate对象,这个对象是Http请求的模版,代码如下:
4.1?动态生成Request
4.2?executeAndDecode
????????经过上述的代码,我们已经将restTemplate拼装完成,上面的代码中有一个executeAndDecode() 方法,该方法通过RequestTemplate生成Request请求对象,然后利用Http Client获取response,来获取响应信息。 ?
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
//转化为Http请求报文
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
//发起远程通信
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;
}
}
?4.3?Client.execute
默认采用JDK的 HttpURLConnection 发起远程调用。
??
|