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

这篇具有很好参考价值的文章主要介绍了Feign踩坑源码分析--@FeignClient注入容器。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一. @EnableFeignClients

  1.1.类介绍

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

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

  1.2.FeignClientsRegistrar 详解

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

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

  1.2.1.registerDefaultConfiguration()

private void registerDefaultConfiguration(AnnotationMetadata metadata,
        BeanDefinitionRegistry registry) {
    // 获取@EnableFeignClients注解的属性和值
    Map<String, Object> 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"));
    }
}

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

  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<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
                    updatedDefinitions.addAll(this.beanDefinitionNames);
                    updatedDefinitions.add(beanName);
                    this.beanDefinitionNames = updatedDefinitions;
                    removeManualSingletonName(beanName);
                }
            }
            ..........
    }

  1.2.2.registerFeignClients()

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

    public void registerFeignClients(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        //获取一个扫描器
        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        //设置资源路径
        scanner.setResourceLoader(this.resourceLoader);
        //包路径
        Set<String> basePackages;
        //获取EnableFeignClients注解的参数和值,有5个值,其中clients对应的class[0]是没有值的
        Map<String, Object> 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<String> 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<BeanDefinition> 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<String, Object> attributes = annotationMetadata
                            .getAnnotationAttributes(
                                    FeignClient.class.getCanonicalName());

                    String name = getClientName(attributes);
                    //再次更新配置
                    registerClientConfiguration(registry, name,
                            attributes.get("configuration"));
                    //注册
                    registerFeignClient(registry, annotationMetadata, attributes);
                }
            }
        }
    }

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

  1.2.2.2.registerFeignClient
    //定义一个 FeignClientFactoryBean bean定义构造器(重点
    BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
    ..........
    //前面根据attributes进行属性赋值后注入到Spring容器中
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);

二.@FeignClient

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

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

  2.1.getTarget()

	<T> T getTarget() {
		//获取创建feign实例的工厂
		FeignContext context = this.applicationContext.getBean(FeignContext.class);
		//获取feign构造器
		Feign.Builder builder = feign(context);
		//url拼接
		if (!StringUtils.hasText(this.url)) {
			//拼接name
			if (!this.name.startsWith("http")) {
				this.url = "http://" + this.name;
			}
			else {
				this.url = this.name;
			}
			//拼接path
			this.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 unwrap
				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));
	}

  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> T loadBalance(Feign.Builder builder, FeignContext context,
            HardCodedTarget<T> 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?");
    }

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

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

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

 2.1.3. HystrixTargeter#target

class HystrixTargeter implements Targeter {

@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
        FeignContext context, Target.HardCodedTarget<T> target) {
    // 不是同一种类型,进入feign#target方法
    if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
        return feign.target(target);
    }
    ......
}

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

 2.1.4. Feign.Builder#target

    public <T> T target(Target<T> 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> T newInstance(Target<T> target) {
    //得到feign类的定义方法
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    //定义方法存放集合
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    //默认方法存放集合
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
    //对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;
  }

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

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

 三.总结

  主要应用了FactoryBean的特性getObject()进行jdk动态创建一个ReflectiveFeign的代理对象。文章来源地址https://www.toymoban.com/news/detail-438063.html

到了这里,关于Feign踩坑源码分析--@FeignClient注入容器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【踩坑日记】feign调用时报错:java.lang.IllegalStateException: RequestParam.value() was empty on parameter 0

    SpringCloud微服务,使用feign进行服务间的调用 服务启动后出现异常: IllegalStateException: RequestParam.value() was empty on parameter 0 非法状态异常。大概意思是:第 0 个参数绑定的 value 值为空。也就是说第 0 个参数没有正确的绑定请求的参数映射。 这个错误产生的原因是我们使用了

    2024年02月05日
    浏览(38)
  • Spring Cloud Feign MultipartFile文件上传踩坑之路(包含前端文件上传请求、后端文件保存到aliyun-oss文件服务器)

    文件上传组件用的是ant-design的a-upload组件,我的界面如下所示: 文件上传请求API: FileUtils.js 需要注意的只有FileUtils.js定义的uploadApi请求函数,其中 URL 为后端请求接口(“/imageConvert/upload”),文件上传方法必须定义为 POST ,在 headers 加入’Content-type’: ‘multipart/form-data’,后端

    2024年02月12日
    浏览(49)
  • Spring Cloud 实战 | 解密Feign底层原理,包含实战源码

    专栏集锦,大佬们可以收藏以备不时之需 Spring Cloud实战专栏:https://blog.csdn.net/superdangbo/category_9270827.html Python 实战专栏:https://blog.csdn.net/superdangbo/category_9271194.html Logback 详解专栏:https://blog.csdn.net/superdangbo/category_9271502.html tensorflow专栏:https://blog.csdn.net/superdangbo/category_869

    2024年02月06日
    浏览(44)
  • .NET 6 整合 Autofac 依赖注入容器

    一行业务代码还没写,框架代码一大堆,不利于学习。 常看到java的学习资料或博客,标题一般为《SpringBoot 整合 XXX》,所以仿照着写了《.NET 6 整合 Autofac 依赖注入容器》这样一个标题。 以下是我自己的用法,可能不是最佳实践。 NuGet搜索并安装: Autofac Autofac.Extensions.Depe

    2023年04月26日
    浏览(48)
  • Spring的简介ioc容器及注入方式

    Spring是一个开源框架,它由Rod Johnson创建。它是为了解决企业应用开发的复杂性而创建的。    Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。    然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Sprin

    2024年02月11日
    浏览(34)
  • Spring框架IOC容器和DI依赖注入

    IOC(Invertion Of Control):控制反转,使用对象时,由使用new创建对象转变为由外部提供对象,此过程中对象的创建控制权由程序转移到外部的思想称之为控制反转. DI(Dependency Injection):依赖注入,在容器中建立bean与bean之间的关系的过程,称之为依赖注入 pom.xml依赖引入 BookDao BookDaoImpl Book

    2023年04月09日
    浏览(89)
  • Spring——Bean注入几种方式(放入容器)

    个人博客: 全是干货,相信不会让你失望 1.XML方式注入 在现在这个Springboot横行的年代,以XML来注入的方式可能已经不多见了,因为压根用不着,但毕竟是注入方式之一也得提一提,这种方式就是依赖于XML的解析来获取我们需要注入的Bean对象 常见的方式有:set方法注入、构

    2024年02月01日
    浏览(44)
  • 将Bean注入Spring容器的五种方式

    将bean放入Spring容器中有哪些方式? 我们知道平时在开发中使用Spring的时候,都是将对象交由Spring去管理,那么将一个对象加入到Spring容器中,有哪些方式呢,下面我就来总结一下 这种方式其实也是我们最常用的一种方式,@Configuration用来声明一个配置类,然后使用 @Bean 注解

    2024年02月05日
    浏览(36)
  • 使用@Configuration和@Bean给spring容器中注入组件

    以前我们是使用配置文件来注册bean的,现如今可以用@Configuration 来代替配置文件。

    2024年02月11日
    浏览(40)
  • spring ico容器 spring注入方式 spring与tomcat整合

    目录 一、简介 1、什么是spring? 2、目的 3、功能及使用范围 二、spring IOC 1、ioc的理解 2、开发人员可达到的目的 3、分析实现 4、bean配置 三、spring IOC的注入方式 1、set方法属性注入 2、构造注入 3、自动装配 四、spring与tomcat的整合/spring与web容器的整合 五、spring AOP 1、aop的特

    2024年02月12日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包