栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

浅谈SpringCloud-OpenFeign的配置使用和分析

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

浅谈SpringCloud-OpenFeign的配置使用和分析

SpringCloud-OpenFeign的配置使用和分析

目录
  • SpringCloud-OpenFeign的配置使用和分析
    • 1.说明
    • 2.配置和使用
    • 3.通过源码浅谈service接口可以@Autowired的问题
    • 4.结语

1.说明

​ Feign是声明性的web服务客户端。 它使编写web服务客户端更加容易。要使用Feign,请创建一个接口并对其进行注释。它具有可插入的注释支持,包括Feign注释和JAX-RS注释。Feign还支持可插拔编码器和解码器。Spring Cloud添加了对Spring MVC注释的支持,并支持使用Spring Web中默认使用的同一HttpMessageConverters。Spring Cloud集成了Ribbon和Eureka以在使用Feign时提供负载平衡的http客户端。

​ 简单的说,使用Feign进行rest服务请求调用更偏向类似于java编程中接口调用,可以认为是对Ribbon + RestTemplate的进一步封装,但是两者还是有区别的!!!!

2.配置和使用
  1. 在API的模块的pom中添加依赖,并创建service虚拟客户端(记住是接口),且在该接口上添加@FeignClient注解(value值为服务实例名称)

    api-pom.xml

    		
            
            
                org.springframework.cloud
                spring-cloud-starter-openfeign
            
    

    DeptClientService.java

    package com.laoliu.springcloud.service;
    
    import com.laoliu.springcloud.pojo.Dept;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    
    import java.util.List;
    
    @FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT") //value为服务实例名称
    // 以这种格式,通过源码可以知道,届时将会被拼接为http://SPRINGCLOUD-PROVIDER-DEPT/rest服务名
    public interface DeptClientService {
        @GetMapping("/dept/get/{id}")
        public Dept queryById(@PathVariable("id") Long id);
    
        @PostMapping("/dept/add")
        public boolean addDept(Dept dept);
    
        @GetMapping("/dept/list")
        public List queryAll();
    }
    

    项目模块结构图:

  2. 在consumer模块添加依赖,启动类上添加 @EnableFeignClients,在controller层中@Autowired 获取ApiService接口并使用

    consumer-pom.xml

    
            
                com.laoliu
                springcloud-api
                1.0-SNAPSHOT
            
    
            
                org.springframework.boot
                spring-boot-starter-web
            
    
            
                org.springframework.boot
                spring-boot-devtools
            
    
            
                org.springframework.cloud
                spring-cloud-starter-netflix-eureka-client
                3.1.2
            
            
                org.springframework.cloud
                spring-cloud-starter-config
            
            
            
                org.springframework.cloud
                spring-cloud-starter-bootstrap
            
    
            
            
            
                org.springframework.cloud
                spring-cloud-starter-openfeign
            
        
    

    application.yml

    server:
      port: 80
    
    eureka:
      client:
        register-with-eureka: false # false 不是服务提供者,不需要注册到Eureka中
        fetch-registry: true # true 消费者需要检索注册中心服务才能调用实例,否则找不到实例,调用失败,报错!!!!!
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
    
    

    启动类

    package com.laoliu.springcloud;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    @SpringBootApplication
    @EnableEurekaClient //开启Eureka
    @EnableFeignClients //开启Feign
    public class DeptConsumerOpenFeign80 {
        public static void main(String[] args) {
            SpringApplication.run(DeptConsumerOpenFeign80.class);
        }
    }
    
    

    Controller层的类

    package com.laoliu.springcloud.controller;
    
    import com.laoliu.springcloud.pojo.Dept;
    import com.laoliu.springcloud.service.DeptClientService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;
    
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    @RestController
    public class DeptConsumerController {
    
        @Autowired
        private DeptClientService clientService; //从Spring容器中拿到服务接口
    
        @PostMapping("/consumer/dept/add")
        public boolean add(Dept dept){
            return this.clientService.addDept(dept);    // 方法调用
        }
    
        @GetMapping("/consumer/dept/get/{id}")
        public Dept get(@PathVariable("id") Long id){
            return this.clientService.queryById(id);    // 方法调用
        }
    
        @GetMapping("/consumer/dept/list")
        public List getAll(){
            return this.clientService.queryAll();   // 方法调用
        }
    
    }
    
    

    通过上述的调用方式,可以看出很符合我们熟悉的java接口调用开发,相对Ribbon + RestTemplate来说,可读性较强,但是性能也相比较较低。

    项目模块结构图:

  1. 启动Eeruka注册中心服务,provider服务和consumer服务,进入浏览器发送请求,查询对应结果。

    通过结果可以看出,Feign的确实现了负载均衡,且默认为轮询策略,其实Feign不是做负载均衡的,负载均衡是Ribbon的功能,Feign只是集成了Ribbon,负载均衡的功能还是feign内置的Ribbon在做,而不是feign。

    注:还不知道Eeruka如何使用的,请看我之前写的一篇”SpringCloud-Eureka配置“博客!

    OpenFeign的配置与使用就此告一段落,其他详细的配置可以去官网查阅!!

3.通过源码浅谈service接口可以@Autowired的问题

现在让我们看看@FeignClient的源码


@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface FeignClient {
    @AliasFor("name")
    String value() default "";

    String contextId() default "";

    @AliasFor("value")
    String name() default "";

    
    @Deprecated
    String qualifier() default "";

    String[] qualifiers() default {};

    String url() default "";

    boolean decode404() default false;

    Class[] configuration() default {}; //配置类,可自行实现

    Class fallback() default void.class;

    Class fallbackFactory() default void.class;

    String path() default "";

    boolean primary() default true;
}

由上可以看出该注解没有复合@Configuration或@Component,那么通过它标注的服务接口又是如何被Spring托管的呢?

对此我从@EnableFeignClients注解入手,源码如下:

@EnableFeignClients


@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients {
    String[] value() default {};

    String[] basePackages() default {};

    Class[] basePackageClasses() default {};

    Class[] defaultConfiguration() default {};

    Class[] clients() default {};
}

我们可以看到该注解中import了FeignClientsRegistrar.class,再加上在启动类上,配合@SpringBootApplication,该类最后将被实现,对此我们进一步分析该类:

FeignClientsRegistrar.class(部分相关源码)


class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    private ResourceLoader resourceLoader;
    private Environment environment;

        
    FeignClientsRegistrar() {
    }

    static String getName(String name) {
        if (!StringUtils.hasText(name)) {
            return "";
        } else {
            String host = null;

            try {
                String url;
                if (!name.startsWith("http://") && !name.startsWith("https://")) {
                    // 由此可以看出届时请求将会被拼接为http://服务实例名称
                    url = "http://" + name;
                } else {
                    url = name;
                }

                host = (new URI(url)).getHost();
            } catch (URISyntaxException var3) {
            }

            Assert.state(host != null, "Service id not legal hostname (" + name + ")");
            return name;
        }
    }

   		

    public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // 集合,不重复
        LinkedHashSet candidateComponents = new LinkedHashSet();
        Map attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
        Class[] clients = attrs == null ? null : (Class[])((Class[])attrs.get("clients"));
        if (clients != null && clients.length != 0) {
            Class[] var12 = clients;
            int var14 = clients.length;

            for(int var16 = 0; var16 < var14; ++var16) {
                Class clazz = var12[var16];
                candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
            }
        } else {
            ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
            scanner.setResourceLoader(this.resourceLoader);
            
            // 查询FeignClient,即扫描过滤出所有的带@FeignClient
            scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
            Set basePackages = this.getBasePackages(metadata);
            Iterator var8 = basePackages.iterator();

            while(var8.hasNext()) {
                String basePackage = (String)var8.next();
                
                //将basePackage下的所有带@FeignClient标识的接口添加到candidateComponents中
                candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
            }
        }

        Iterator var13 = candidateComponents.iterator();

        // 迭代器,遍历进行FeignClient注册,注册到Spring容器中
        while(var13.hasNext()) {
            BeanDefinition candidateComponent = (BeanDefinition)var13.next();
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition)candidateComponent;
                //annotationMetadata 对应的接口
                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 = this.getClientName(attributes);
                // 拿到@FeignClient中configuration并进行配置替换
                this.registerClientConfiguration(registry, name, attributes.get("configuration"));
                // 将接口和@FeignClient中参数配置并注册到Spring容器中
                this.registerFeignClient(registry, annotationMetadata, attributes);
            }
        }

    }
	// 注册详细源码如下
    private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map attributes) {
        String className = annotationMetadata.getClassName();
        Class clazz = ClassUtils.resolveClassName(className, (ClassLoader)null);
        ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory ? (ConfigurableBeanFactory)registry : null;
        String contextId = this.getContextId(beanFactory, attributes);
        String name = this.getName(attributes);
        FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
        factoryBean.setBeanFactory(beanFactory);
        factoryBean.setName(name);
        factoryBean.setContextId(contextId);
        // clazz接口类型设置,即@FeignClient标注下的接口
        factoryBean.setType(clazz);
        factoryBean.setRefreshableClient(this.isClientRefreshEnabled());
        BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
            factoryBean.setUrl(this.getUrl(beanFactory, attributes));
            factoryBean.setPath(this.getPath(beanFactory, attributes));
            factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
            Object fallback = attributes.get("fallback");
            if (fallback != null) {
                factoryBean.setFallback(fallback instanceof Class ? (Class)fallback : ClassUtils.resolveClassName(fallback.toString(), (ClassLoader)null));
            }

            Object fallbackFactory = attributes.get("fallbackFactory");
            if (fallbackFactory != null) {
                factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class)fallbackFactory : ClassUtils.resolveClassName(fallbackFactory.toString(), (ClassLoader)null));
            }

            return factoryBean.getObject();
        });
        definition.setAutowireMode(2);
        definition.setLazyInit(true);
        this.validate(attributes);
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
        beanDefinition.setAttribute("factoryBeanObjectType", className);
        beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
        boolean primary = (Boolean)attributes.get("primary");
        beanDefinition.setPrimary(primary);
        String[] qualifiers = this.getQualifiers(attributes);
        if (ObjectUtils.isEmpty(qualifiers)) {
            qualifiers = new String[]{contextId + "FeignClient"};
        }

        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
        // 注册为bean到容器中
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
        this.registerOptionsBeanDefinition(registry, contextId);
    }
    
 		

结论: 通过短暂的源码分析可以看出,虽然@FeignClient没有复合@Configuration或@Component,但在注册FeignClientsRegistrar的过程中,已经通过 BeanDefinitionReaderUtils.registerBeanDefinition将所标识的接口注册到容器中,所以可以使用@Autowired拿到容器中对应的service接口。

4.结语

源码部分没有很详细深究,如果有大佬深入了解过源码,上文若有错的地方请大佬不吝指出,我将进行改正和学习,最后感谢各位的观看!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/884711.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号