背景
(可以略过的废话)前段时间一个朋友在做spring security相关配置时遇到一些问题,请我帮忙解决一些问题,由于我自身并没有使用过security,所以花了一段时间去学习spring security的使用和解析了一下spring security源码。
时至今日,终于可以再次重新开始学习spring cloud相关的内容了~
1.@EnableFeignClients注解的默认配置
1.1 basePackages
扫描@FeignClient注解的包路径,扫描这些包下的所有类,获取被@FeignClient注解的接口,生成代理对象。
1.2 basePackageClasses
这个其实跟上面的很像,就是做一些标记类,然后这些标记类所在的包及其子包里的类都会被扫描。
1.3 defaultConfiguration
这个是对所有feign 客户端进行配置设定使用的。
可以覆写feign client的相关组件定义
feign.codec.Decoder : 解码器,也就是feign远程调用其他接口获取结果后进行解码成对应的对象
feign.codec.Encoder : 编码器,feign在远程调用之前,需要先将请求参数进行编码成对应的表单或者json之类的。
feign.Contract :注解解释器,也就是用来解读@FeignClient标注的interface上的所有注解信息,转化为对应的url和参数。因为web框架比较多。每种注解不一致。feign是Netflix内部使用的,所以他们并没有对springmvc进行支持。后来openFeign中才添加对spring mvc的支持(也就是SpringMvcContract)。
1.4 clients
会基于这几个类和类所在的包进行扫描的。
2.FeignClientsRegistrar之EnableFeignClients导入
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eA05YUGl-1626782964309)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/fc211fd6-1e44-4865-9252-f13355526e51/Untitled.png)]](/https://i-blog.csdnimg.cn/blog_migrate/0bbf85acd9df503532e37b217eaaaf2b.png)
FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口。
而对于spring来说,实现了ImportBeanDefinitionRegistrar的类,会在org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromRegistrars方法中会进行registerBeanDefinitions方法的调用。
所在在FeignClientRegistrar中,我们只需要关注registerBeanDefinitions方法即可。
3.registerBeanDefinitions
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 默认配置注册
registerDefaultConfiguration(metadata, registry);
// 注册FeignClient
registerFeignClients(metadata, registry);
}
3.1 registerDefaultConfiguration 设置默认配置
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 获取EnableFeignClients注解里面的参数信息
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"));
}
}
上面就是从注解中获取配置信息,然后将配置信息注入到spring容器中。
具体如何注入的呢?
首先通过BeanDefinitionBuilder构建一个FeignClientSpecification对象,然后注入上面生成的name和配置信息。
最后通过BeanDefinitionRegistry将由BeanDefinitionBuilder构建出的FeignClientSpecification注入到spring容器中去。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sEOhZETt-1626782964312)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/dcb6a956-997f-4222-9d28-36bcc5d8f1d3/Untitled.png)]](/https://i-blog.csdnimg.cn/blog_migrate/627e239b9f6a8b05a94c12fa4c2f5aa0.png)
从上面我们知道,如果我们要自定义编码器Encoder,解码器Decoder,注解解释器Contract时,实际上配置是交给FeignClientSpecification管理的。
其实这里可以深究一下FeignClientSpecification 这个东西在哪用了,毕竟我们如果对组件进行了修改或者自定义,那么如何融合到feign的流程中,也是很有比较有探究价值的。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vIzOUtE8-1626782964314)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/e40a4aa4-8fd6-4701-8111-c6d99e29bfeb/Untitled.png)]](/https://i-blog.csdnimg.cn/blog_migrate/33858a124ea94bf3b4d3f318c41165fb.png)
从上图我们可以看到,总共只有6处使用到这个类。其中两处需要关注的地方我已经标注出来。
第一个使用地:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FHrRpytg-1626782964318)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/63a6b710-f373-4e50-bfb5-95c957d3764d/Untitled.png)]](/https://i-blog.csdnimg.cn/blog_migrate/6412bdcd008871fd109ce33a56f798bb.png)
从这个类的名字可以看出,这是个自动装配类,还是Feign直接相关的自动装配。那么这个类所在的包的spring.factories就是我们后面探索的关键所在了。
这里可以看到,FeignClientSpecification 作为bean被Autowired到一个List集合中去了。那么后续的使用只需要关注configurations了。
这里就先不深入了,在后面探索spring.factories时,再深入研究。
第二个使用地:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fmsbMENK-1626782964321)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/5a0bf43d-c151-45d2-af43-085cf89b53bf/Untitled.png)]](/https://i-blog.csdnimg.cn/blog_migrate/912c8694e2532cded9de623988bc6cf9.png)
FeignContext,见名知意,就是Feign的上下文。而上下文是跟作为泛型的FeignClientSpecification 有关的。
显然FeignContext也是有关于FeignClientSpecification 的操作的。
3.2 registerFeignClients 扫描@FeignClient生成代理对象注册到spring容器中
1.获取要扫描的包或者类
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AezDP45k-1626782964323)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/0621b825-c1db-43e4-b948-35a5239390e8/Untitled.png)]](/https://i-blog.csdnimg.cn/blog_migrate/ee8d777e852581e32ccfde2da678a3e7.png)
2.基于@EnableFeignClient的clients配置
final Set<String> clientClasses = new HashSet<>();
basePackages = new HashSet<>();
for (Class<?> clazz : clients) {
// 获取clients里面配置的class的包名,并加入到包扫描集合中
basePackages.add(ClassUtils.getPackageName(clazz));
// FeignClient类
clientClasses.add(clazz.getCanonicalName());
}
// 测试使用的
AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
@Override
protected boolean match(ClassMetadata metadata) {
String cleaned = metadata.getClassName().replaceAll("\\$", ".");
return clientClasses.contains(cleaned);
}
};
// 扫描器添加过滤器,一个是测试使用的,一个是基于FeignClient注解的过滤器
scanner.addIncludeFilter(
new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
所以上面其实就是干了一个事情,就是将clients配置的类所在的包路径提取出来,然后再走包扫描。
3.包扫描
for (String basePackage : basePackages) {
// 扫描出包含@FeignClient注解的类
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// 只要注解类bean声明
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
// 判断这个@FeignClient注解的这个class是不是interface
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
// 获取注解配置的属性信息
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
// 这里是获取客户端名称,用于注册到spring中去
String name = getClientName(attributes);
// 在EnableFeignClient中可以配置全局FeignClient的配置
// 在FeignClient中可以针对单个进行配置
// 这也就是为啥我们在FeignAutoConfiguration中看到FeignClientSpecification
// 是一个集合的原因了。
registerClientConfiguration(registry, name,
attributes.get("configuration"));
// 注册FeignClientFactoryBean到spring容器中
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
关于ClassPathScanningCandidateComponentProvider 如何扫描到@FeignClient类:
这里插入一个spring的知识点,ClassPathScanningCandidateComponentProvider 这个类就是用来基于路径来扫描合适的组件的。
这其中有个很重要的东西,就是我们如何通过类路径扫描类并获取到自己想要的组件呢?
就是通过ClassPathScanningCandidateComponentProvider 的isCandidateComponent方法来进行判断的。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9aphUeOv-1626782964326)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/39594525-83f0-471c-9cd7-3e8d125ab279/Untitled.png)]](/https://i-blog.csdnimg.cn/blog_migrate/f34d3b36b3e0fe61dd81dc0a2b5a11d9.png)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-awqBm1IC-1626782964328)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/53377eea-a5bb-4dd6-953d-3d4705cdc6d4/Untitled.png)]](/https://i-blog.csdnimg.cn/blog_migrate/1fb3a75db1870b7956e89676f643830c.png)
从上面我们可以看到,我们创建了一个注解类型过滤器,并注入到了ClassPathScanningCandidateComponentProvider 的includeFilters中,然后在扫描的过程中通过通过AnnotationTypeFilter去匹配扫描出来的每个类。
至此,我们就知道如何获取@FeignClient注解的类了。
4.registerFeignClient 注册FeignClient到spring中
其实这里说将FeignClient注册到spring中其实,不是很正确。其实是注册了一个FeignClientFactoryBean到spring容器中去了。
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
// 1.获取被@FeignClient注解的Interface的名称
String className = annotationMetadata.getClassName();
// 2.创建一个FeignClientFactoryBean的spring bean构建器
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
// 3.参数校验:主要是关于失败回调的配置
validate(attributes);
// 4.设置FeignClientFactoryBean 的各个参数
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);
// 远程调用404是否解码
definition.addPropertyValue("decode404", attributes.get("decode404"));
// 失败回调
definition.addPropertyValue("fallback", attributes.get("fallback"));
// 失败回调工厂
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
// 设置注入类型,通过类型进行注册(也就是className)
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
// 别名
String alias = contextId + "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;
}
// 通过holder,可以通过className,也可以通过别名来获取这个FeignClient
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
// 5.注册FeignClientFactoryBean到spring容器中
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
从上面我们可以看到,我们可以看到所有关于FeignClient的信息都被封装到了FeignClientFactoryBean中,其实这个时候并没有创建FeignClient对象。
真正的FeignClient对象是在FeignClientFactoryBean里通过getObject()方法创建的。
这里其实又涉及到Spring的一个知识点:
关于FactoryBean对象,在spring初始化bean过程中会通过org.springframework.beans.factory.support.FactoryBeanRegistrySupport#getObjectFromFactoryBean 方法来对实现了FactoryBean的bean进行getObject()方法调用。通过这个getObject就能获取这个工厂bean创建的对象。
5.FeignClientFactoryBean#getObject获取真正的FeignClient
@Override
public Object getObject() throws Exception {
return getTarget();
}
<T> T getTarget() {
FeignContext context = applicationContext.getBean(FeignContext.class);
// 获取feign构建器
Feign.Builder builder = feign(context);
// 一般我们都是通过注册中心+feign来使用,所以基本都是进入下面这个判断
// 然后就返回了。
// 也就是说url我们都不会填的
if (!StringUtils.hasText(this.url)) {
// 这里就是判断是不是基于注册中心进行远程调用
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
// 将feign与负载均衡器和限流器进行结合,生成一个对象进行返回
// 这里层层包裹
// Targeter: 有两个实现DefaultTargeter,HystrixTargeter
// 默认为HystrixTargeter,这个其实针对的是HystrixFeign来进行配置的
// 最终这里会创建出一个this.type的代理对象,
// 这个对象持有LoadBalanceFeignClient
// 以及LoadBalancerFeignClient
// 这个里面的东西比较复杂,稍后单独开一篇解析一下
return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
}
// 下面的可以先不看了,一般走不到。
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.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
// 指定了url,这个时候就不需要负载均衡了,所以要从负载均衡器中取出发送http请求的
// client
client = ((LoadBalancerFeignClient)client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}
基于建造者模式构建Feign
protected Feign.Builder feign(FeignContext context) {
// feign客户端日志工厂
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);
// @formatter:off
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger) // feign独立日志配置
.encoder(get(context, Encoder.class)) // 编码器
.decoder(get(context, Decoder.class)) // 解码器
.contract(get(context, Contract.class)); // 注解解释器
// @formatter:on
// 设置一下配置
// 1.连接超时时间
// 2.重试次数
// 3.异常解码器
// 4.请求拦截器
// 5.404解码器是否开启设置
// 6. 自定义编码器
// 7. 自定义解码器
// 8. 自定义注解解码器(就是解析注解为url)
configureFeign(context, builder);
return builder;
}
4.小结
单从@EnableFeignClient注解引入,即可看到很多信息。其中最主要的就是Encoder,Decoder,Contract这三个组件。
还有FeignClientFactoryBean如何将Feign构建器,FeignContext,HysterixFeign,LoadBalancerFeignClient一起整合到带有@FeignClient注解的接口的代理类中。
举例说明:
@FeignClient
@RequestMapping("/user")
public interface ServiceAController{
@RequestMapping("/hello")
void sayHello();
}
流程:
1.在启动阶段首先会创建一个FeignClientFactoryBean,它包含ServiceAController的所有信息,其中包含@FeignClient注解的参数,类名等信息
2.FeignClientFactoryBean 会创建一个Feign.Builder ,也就是Feign的构建器,并往里面添加各种配置和组件
3.获取LoadBalancerFeignClient,添加到Feign.Builder中
4.获取HystrixTargeter,如果feign开启了Hystrix,则进行相关失败回调设置。
5.通过Feign.Builder.build()创建一个ReflectiveFeign对象。此时ReflectiveFeign对象中包含LoadBalancerFeignClient,FeignClientFactoryBean,hystrix配置等信息。
6.通过ReflectiveFeign.newInstance方法创建一个ServiceAController的代理对象,其中InvocationHandler为ReflectiveFeign的内部类,它在创建时会带入ReflectiveFeign所携带的内容。
至此,我们就了解了被@FeignClient注解的接口的代理类的实现方式了。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OmnSfWtW-1626782964330)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/03fbfc54-c0ef-4c16-a345-57df8d89d9fe/Untitled.png)]](/https://i-blog.csdnimg.cn/blog_migrate/6be71046011eb60b5c663358d4390910.png)
本文详细探讨了@EnableFeignClients注解的配置细节,包括默认配置、自定义组件及FeignClient的注册过程。重点讲解了FeignClientRegistrar如何工作,以及FeignClientFactoryBean如何整合组件创建代理。
&spm=1001.2101.3001.5002&articleId=118943509&d=1&t=3&u=c678d97ce4a1445e86841630a282a893)
2103

被折叠的 条评论
为什么被折叠?



