目录
一、@import
二、启动加载FeignClientsRegistrar
1、启动类上添加的@EnableFeignClients开启feign支持
2、会通过@Import(FeignClientsRegistrar.class)动态注入Bean接口
3、动态装载
4、重点:registerBeanDefinitions主要包含两个函数内容
5、registerFeignClient作用
6、registerBeanDefinitions小结
三、动态代理
1、HystrixTargeter
2、FeignInvocationHandler
四、openfeign的远程调用
五、总结
首先谈谈@import(value="class")普通用法引入普通class类,将class注入到IOC容器中。下图还有两种用法,区别于实现方法不同,即实现ImportBeanDefinitionRegistrar接口和实现ImportSelector接口。
FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar,SpringBoot启动的时候,会去调用这个类中的registerBeanDefinitions来实现动态Bean的装载。
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 将@EnableFeignClients注解中的defaultConfiguration属性注册到beanDefinitionMap缓存
// 如果启动时不配置defaultConfiguration的话就是空数组
registerDefaultConfiguration(metadata, registry);
// 1) 扫描所有@FeignClient注解的接口,即扫描到所有Feign接口
// 2) 将每个@FeignClient注解的configuration属性注册到一个缓存map
// 3) 根据@FeignClient注解元数据生成 FeignClientFactoryBean 的BeanDefinition,
// 并将这个BeanDefinition注册到一个缓存map
registerFeignClients(metadata, registry);
}
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//获取EnableFeignClients注解属性
Map 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();
}
//将defaultConfiguration属性注册到beanDefinitionMap缓存
//具体由DefaultListableBeanFactory类实现
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
LinkedHashSet candidateComponents = new LinkedHashSet<>();
//扫描EnableFeignClients的属性
Map attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
//AnnotationTypeFilter(FeignClient.class)它是用来判断一个类是不是被Spring扫描到的
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是Spring提供的工具,可以按自定义的类型,查找classpath下符合要求的class文件。
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
Set basePackages = getBasePackages(metadata);
//主要就是将@FeignClient修饰的class封装到set中
for (String basePackage : basePackages) {
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
else {
for (Class> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
//遍历set集合,实质上set集合内容就是@FeignClient修饰的客户端信息
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 attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
//将configuration属性注册到beanDefinitionMap缓存
//具体由DefaultListableBeanFactory类实现
registerClientConfiguration(registry, name,
attributes.get("configuration"));
//将FeignClient客户端信息通过生成BeanDefinition注册到IOC容器
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
registerFeignClient方法就是去组装BeanDefinition,定义Bean,然后注册到Spring IOC容器
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map attributes) {
// 获取@FeignClient修饰的接口的class路径
String className = annotationMetadata.getClassName();
// 生成一个beanFactory,其会为FeignClientFactoryBean生成一些必要的组件
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";
// 获取到FeignClientFactoryBean的beanDefinition
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;
}
// 将beanDefinition封装到holder,后续通过holder可以获取到这个beanDefinition
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
// 把BeanDefinition的这个bean定义注册到IOC容器
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
开启扫描FeignClient的入口:
启动类上添加的@EnableFeignClients
注解会通过@Import
注解在SpringBoot启动流程中将 ImportBeanDefinitionRegistrar
接口的实现类FeignClientsRegistrar
注入到启动类的ConfigurationClass
的属性中,在注册启动类的BeanDefinition
时,会遍历调用其@Import
的所有ImportBeanDefinitionRegistrar
接口的 registerBeanDefinitions()
方法。
扫描FeignClient
:
拿到@EnableFeignClients
注解中配置的扫描包路径相关的属性,得到要扫描的包路径; 获取到扫描器ClassPathScanningCandidateComponentProvider
,然后给其添加一个注解过滤器(AnnotationTypeFilter)
,只过滤出包含@FeignClient
注解的BeanDefinition
; 扫描器的findCandidateComponents(basePackage)
方法从包路径下扫描出所有标注了@FeignClient
注解并符合条件装配的接口;然后将其在BeanDefinitionRegistry
中注册一下
在扫描完前面的注解后,还会做一件事就是加载断路器,在我使用的版本目前还是内置的HystrixTargeter(高版本移除了Hystrix,采用
Spring Cloud Circuit Breaker
做限流熔断)
FactoryBean用来创建代理Bean。FeignClientFactoryBean实现了FactoryBean.getObject()接口,执行时通过调用getObject()来获取被代理Bean的实例。看看getObject()具体做了什么事情
@Override
public Object getObject() throws Exception {
return getTarget();
}
/**
* @param the target type of the Feign client
* @return a {@link Feign} client created with the specified data and the context
* information
*/
T getTarget() {
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
//如果url为空,则走负载均衡,生成有负载均衡功能的代理类
if (!StringUtils.hasText(url)) {
if (!name.startsWith("http")) {
url = "http://" + name;
}
else {
url = name;
}
url += cleanPath();
// 判断是否需要LoadBalance,如果需要,则通过LoadBalance的方法来设置。实际上他们最终调用的是Target.target()方法。
return (T) loadBalance(builder, context,
new HardCodedTarget<>(type, name, url));
}
// 这是url不为空的情况,即采用直连方式访问提供者,则生成默认的代理类
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
// 从Spring子容器中获取Client
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);
}
// todo 生成默认的代理类
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
}
最终调用的是Target.target()方法,由于该版本openfeign内置的是hystrix实现Target接口。所以最终加载的的就是hystrix那套
被声明为@FeignClient注解的类,在被注入时,最终会生成一个动态代理对象FeignInvocationHandler,在远程调用getStock()时就会进入到invoke()中
主要采用JDK动态代理的方式,FeignInvocationHandler实现InvocationHandler接口的invoke(),在该方法中通过调用dispatch.get(method).invoke(args)设置SynchronousMethodHandler处理器,用来实现同步远程调用。
//FeignInvocationHandler
private final Target target;
//处理器缓存
private final Map dispatch;
//jdk动态代理
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
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();
}
//获取对应的远程调用处理器
return dispatch.get(method).invoke(args);
}
上面的dispatch在启动时,通过加载@FeignClient修饰的类信息,调用create()方法进行加载处理器
上面说了在客户端发起远程调用时,具体执行的就是dispatch.get(method).invoke(args)
而它返回的是某个接口处理器SynchronousMethodHandler.invoke()的执行结果。
invoke()中首先就会创建一个http协议的RequestTemplate 请求模板对象
//SynchronousMethodHandler.class
@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 {
return executeAndDecode(template, options);
} catch (RetryableException e) {
try {
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
后续执行executeAndDecode(),从名字就能看出来该方法就是执行http请求和解码获取Response 返回结果
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
//通过模板对象转化为http的Reuqest报文
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
//建立connection,发起http请求
response = client.execute(request, options);
//将返回报文build到response中,后续就是进行返回
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
之前看网上都说openfeign用的是Ribbon那么具体在哪使用了呢?接下来看看 client.execute(request, options)执行了哪些内容。
发起请求是通过LoadBalancerFeignClient实现了Client.execute 接口
所以进到LoadBalancerFeignClient.execute 中可以看到FeignLoadBalancer.RibbonRequest。谜底就在这!!!以及拿到了properties配置,记得openfeign和Ribbon会有一个冲突的地方就是都设置了过期时间的情况下,只会采取一个配置 feign 优先。(不过后续OpenFeign2020.0.X往后貌似不再使用Ribbon做负载均衡了)
@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()可以想到了是要去干什么事情
- 创建一个可观察对象,一旦订阅,就与服务器异步执行网络调用由负载平衡器选择。如果有任何错误被RetryHandler指定为可检索的,它们将被函数内部使用,而不会被订阅到返回的Observable的Observer观察到。如果重试次数超过了允许的最大次数,则返回的Observable会发出一个最终错误。否则,将发出执行期间的第一个成功结果和重试。(这是官方给出的注释说明,有负载均衡机制,有重试机制)
可以看到在执行execute()方法时,是调用了IClient接口通过FeignLoadBalancer类实现
到了这里就是执行的最底层的Client接口了。
request.client().execute()调用的是Default默认的实现类。到了这就是真正的结合之前的转化好的报文发起http请求。返回Response对象
openfeign简单概括做了几件事
1、启动通过扫描@EnableFeignClients和@FeignClient
2、拿到扫描要注册的Bean信息后,注入到一个名为FeignClientFacotoryBean 的spring容器中
3、spring容器初始化通过JDK动态代理,获取FeignClientFacotoryBean产生的代理对象Proxy
4、在进行远程调用时,请求会进过InvocationHandler统一处理,封装http上下文,拿到了properties配置,配置负载均衡策略,重试策略,发起http请求