当前位置: 首页>资讯 >

Feign踩坑源码分析--@FeignClient注入容器

来源: 博客园 | 时间: 2023-05-10 16:26:19 |


(资料图)

一.@EnableFeignClients  1.1.类介绍

从上面注释可以看出是扫描声明了@FeignClient接口的类,还引入了FeignClientsRegistrar类,从字面意思可以看出是进行了FeignClient 客户端类的注册。

1.2.FeignClientsRegistrar 详解

最主要的一个方法:registerBeanDefinitions注册bean定义信息,主要功能是实现向容器注册Feign客户端配置信息和所有的使用了@FeignClient的类;

1.2.1.registerDefaultConfiguration()
private void registerDefaultConfiguration(AnnotationMetadata metadata,        BeanDefinitionRegistry registry) {    // 获取@EnableFeignClients注解的属性和值    Map defaultAttrs = metadata            .getAnnotationAttributes(EnableFeignClients.class.getName(), true);    // 获取属性中的默认配置 defaultConfiguration,name是main主程序    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"));    }}
1.2.1.1.registerClientConfiguration
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,        Object configuration) {    // 创建一个 FeignClientSpecification.class 构造器    BeanDefinitionBuilder builder = BeanDefinitionBuilder            .genericBeanDefinition(FeignClientSpecification.class);    builder.addConstructorArgValue(name);    builder.addConstructorArgValue(configuration);    // 向容器中注册默认配置    registry.registerBeanDefinition(            name + "." + FeignClientSpecification.class.getSimpleName(),            builder.getBeanDefinition());}
1.2.1.2.registerBeanDefinition

核心代码在:org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)            throws BeanDefinitionStoreException {            ............            if (hasBeanCreationStarted()) {                // Cannot modify startup-time collection elements anymore (for stable iteration)                synchronized (this.beanDefinitionMap) {                    //放到beanDefinitionMap,到AbstractApplicationContext#finishBeanFactoryInitialization(beanFactory)中取出进行bean实例化                    //此时是处于invokeBeanFactoryPostProcessors(beanFactory);阶段,进行BeanDefinitionRegistryPostProcessor的处理                    this.beanDefinitionMap.put(beanName, beanDefinition);                    List updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);                    updatedDefinitions.addAll(this.beanDefinitionNames);                    updatedDefinitions.add(beanName);                    this.beanDefinitionNames = updatedDefinitions;                    removeManualSingletonName(beanName);                }            }            ..........    }
1.2.2.registerFeignClients()
public void registerFeignClients(AnnotationMetadata metadata,            BeanDefinitionRegistry registry) {        //获取一个扫描器        ClassPathScanningCandidateComponentProvider scanner = getScanner();        //设置资源路径        scanner.setResourceLoader(this.resourceLoader);        //包路径        Set basePackages;        //获取EnableFeignClients注解的参数和值,有5个值,其中clients对应的class[0]是没有值的        Map attrs = metadata                .getAnnotationAttributes(EnableFeignClients.class.getName());        //定义一个 FeignClient 注解类型过滤器        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(                FeignClient.class);        //clients为class[0],长度为0        final Class[] clients = attrs == null ? null                : (Class[]) attrs.get("clients");        if (clients == null || clients.length == 0) {            //FeignClient 注解过滤器添加到扫描器中            scanner.addIncludeFilter(annotationTypeFilter);            //获取Application对应的根路径包            basePackages = getBasePackages(metadata);        }        //clients不为空:FeignClient 注解过滤器添加到扫描器中、获取包路径        else {            final Set 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)));        }        //遍历包路径,获取标记@FeignClient注解的接口向容器中注入        for (String basePackage : basePackages) {            Set 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");                    //获取@FeignClient的参数和值                    Map attributes = annotationMetadata                            .getAnnotationAttributes(                                    FeignClient.class.getCanonicalName());                    String name = getClientName(attributes);                    //再次更新配置                    registerClientConfiguration(registry, name,                            attributes.get("configuration"));                    //注册                    registerFeignClient(registry, annotationMetadata, attributes);                }            }        }    }
1.2.2.2.registerFeignClient
//定义一个 FeignClientFactoryBean bean定义构造器(重点)    BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);    ..........    //前面根据attributes进行属性赋值后注入到Spring容器中    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
二.@FeignClient

从上面可以看到在注册客户端时注册了一个FeignClientFactoryBean(对于FactoryBean的扩展知识和案例),所以FeignClient的获取是从getObject()获取的:

2.1.getTarget()
 T getTarget() {//获取创建feign实例的工厂FeignContext context = this.applicationContext.getBean(FeignContext.class);//获取feign构造器Feign.Builder builder = feign(context);//url拼接if (!StringUtils.hasText(this.url)) {//拼接nameif (!this.name.startsWith("http")) {this.url = "http://" + this.name;}else {this.url = this.name;}//拼接paththis.url += cleanPath();//创建实例return (T) loadBalance(builder, context,new HardCodedTarget<>(this.type, this.name, this.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 unwrapclient = ((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));}
2.1.1. feign()
//添加日志对象、编码器、解码器、解析规则器    protected Feign.Builder feign(FeignContext context) {        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)                .encoder(get(context, Encoder.class))                .decoder(get(context, Decoder.class))                //SpringMvcContract:对RequestMapping、RequestParam、RequestHeader等注解进行解析                .contract(get(context, Contract.class));        // @formatter:on        configureFeign(context, builder);        return builder;    }
2.1.2.loadBalance()
protected  T loadBalance(Feign.Builder builder, FeignContext context,            HardCodedTarget target) {        //根据context上下文获取客户端实例,client:TraceLoadBalancerFeignClient,负载均衡        Client client = getOptional(context, Client.class);        if (client != null) {            //客户端创建            builder.client(client);            //获取HystrixTargerer            Targeter targeter = get(context, Targeter.class);            //调用HystrixTargeter#target进行实例创建            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.1.3.HystrixTargeter#target
class HystrixTargeter implements Targeter {@Overridepublic  T target(FeignClientFactoryBean factory, Feign.Builder feign,        FeignContext context, Target.HardCodedTarget target) {    // 不是同一种类型,进入feign#target方法    if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {        return feign.target(target);    }    ......}
2.1.4.Feign.Builder#target
public  T target(Target target) {      return build().newInstance(target);    }
2.1.4.1. Feign.Builder#target
public Feign build() {      //同步方法处理工厂,构建了日志级别信息      SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =          new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,              logLevel, decode404, closeAfterDecode, propagationPolicy);      //解析信息:编码、解密、同步方法处理工厂      ParseHandlersByName handlersByName =          new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,              errorDecoder, synchronousMethodHandlerFactory);      //new对象      return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);    }
2.1.4.2.ReflectiveFeign#newInstance
@Override  public  T newInstance(Target target) {    //得到feign类的定义方法    Map nameToHandler = targetToHandlersByName.apply(target);    //定义方法存放集合    Map methodToHandler = new LinkedHashMap();    //默认方法存放集合    List defaultMethodHandlers = new LinkedList();    //对feign类方法进行遍历    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)));      }    }    //jdk动态代理创建对象    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;  }
三.总结

主要应用了FactoryBean的特性getObject()进行jdk动态创建一个ReflectiveFeign的代理对象。

关键词:

 

热文推荐

Feign踩坑源码分析--@FeignClient注入容器

一 & 160;@EnableFeignClients1 1 类介绍从上面注释可以看出是扫描声明了@FeignClient接口的类,还引入了&

2023-05-10

韩民调:过半韩国人主张日本先就历史问题道歉,再改善关系

今年3月,在美国撮合下,尹锡悦政府不惜在历史问题上做出大幅让步以谋求改善韩日关系,意欲加速推进美日韩

2023-05-10

2023毕节中考成绩查询时间及入口_全球微动态

2023年毕节中考成绩查询时间预计是7月7日,2023年毕节中考成绩查询需要准考证号、身份证号、考生号等,每个

2023-05-10

天天实时:越秀·星耀TOD正在销售价格约为27000元/平

【越秀星耀TOD】消息介绍:越秀·星耀TOD正在销售平均价格约为27000元 ㎡,产品为建筑面积约100 125㎡南向

2023-05-10

5月10日 14:44分 深深房A(000029)尾盘放量拉升

截止发稿,深深房A(000029)涨幅10 04%,成交量7 215万手,换手率0 81%。资金流向数据:主力资金净流出65

2023-05-10

肯尼亚国际问题专家:美国监视盟友行为可耻 应自我反省

肯尼亚国际问题专家:美国监视盟友行为可耻应自我反省此前,据美国媒体报道,在多家社交媒体出现的一批疑似

2023-05-10

《北京企业高质量发展评价指标体系》发布会将于4月26日下午2点半在中关村软件园举行 环球时快讯

2023年4月26日下午2点半,《北京企业高质量发展评价指标体系》发布会将在海淀区中关村软件园1号楼信息中心C

2023-05-10

当前播报:玉米饼做的太稀了怎么办?

加点玉米面或者白面即可。玉米面发稀了就再加点玉米面,然后再醒一会儿功夫就开了。如果想有劲道,口感好,

2023-05-10

今日象是会意字还是形声字(什么是形声字,什么是象声字,什么是会意字)

象是会意字还是形声字,什么是形声字,什么是象声字,什么是会意字很多人还不知道,现在让我们一起来看看吧

2023-05-10

青岛拟于5月31日出让3宗商住地 总起价22.21亿元 当前要闻

5月9日,中国土地市场网披露了青岛市黄岛区自然资源局国有土地使用权拍卖出让公告和青岛市自然资源和规划局

2023-05-10

国六b排放标准给予部分车型半年过渡期 不满足RDE要求车辆超189万|时讯

中国网汽车5月10日讯5月9日,生态环境部等5部门联合发布《关于实施汽车国六排放标准有关事宜的公告》,其中

2023-05-10

财政部下达科技馆免费开放补助资金 全国核定预算91680万元

记者从财政部获悉,为保障免费开放科技馆正常运转,经研究,现下达2023年科技馆免费开放补助资金预算,统筹

2023-05-10

瓜迪奥拉谈换人:场上球员表现得很好 我不希望节奏被打乱

球天下5月10日讯,在欧冠半决赛首回合比赛中,曼城在伯纳乌球场与皇家马德里1-1战平。

2023-05-10

欧冠夺冠赔率:曼城1.61大幅领跑皇马第二,国米第三米兰垫底|天天头条

直播吧5月10日讯欧冠半决赛首回合,皇马1-1战平曼城。最新的欧冠赔率如下。曼城1 61国米7 00欧冠半决赛首回

2023-05-10

新西兰航空投资35亿美元用于机队改造,包括购买空客、波音客机_世界热文

新西兰航空5月10日宣布,未来五年,将投资35亿美元用于购买新飞机和现有机队的改造,其中包括购买8架新的波

2023-05-10

这一晚,马莱莱真的让人快乐

依靠他的梅开二度和曹赟定的锦上添花,昨晚申花客场3比1击败深圳队,取得一场酣畅的胜利。

2023-05-10

世界实时:一夜醒来欠地铁600多万?官方回应

近日,“一夜醒来欠了地铁600多万”登上微博热搜,引发网友热议。咋回事?事情源于网友“默默无闻”8日在某

2023-05-10

观焦点:项目成本后评估,真不简单

项目成本后评估,真不简单,财务,成本控制,项目成本

2023-05-10

定西景点宣传片(定西景点) 每日速读

1、贵青山遮阳山鸟鼠山双石门太白山狼渡滩双燕。以上就是【定西景点宣传片,定西景点】相关内容。

2023-05-10

荒野大镖客2商贩该怎么玩(荒野大镖客2商贩怎么玩商贩职业经营心得)

荒野大镖客2商贩该怎么玩?许多小伙伴不知道荒野大镖客2商贩这个职业该怎么玩,能力该怎么搭配。下面小编就

2023-05-10