声明式调用Feign

上一章中讲解了如何使用RestTemplate来消费服务,如何结合Ribbon在消费服务时做负载均衡。本章将讲解Feign的使用,Feign是一个web请求的工具,可以将请求指定到具体的服务上去,在项目中主要用做服务之间的调用。

写一个Feign客户端

  • 加入相关依赖,主要包括feign的起步依赖spring-cloud-starter-feign,Eureka Client的起步依赖spring-cloud-starter-eureka
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
        </dependency>
    </dependencies>
  • 配置文件中做相关配置
server:
  port: 8766
spring:
  application:
    name: eureka-feign-client
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
  • 工程启动类上加上@EnableEurekaClient开启Eureka Client的功能,加上@EnableFeignClients开启Feign Client的功能。
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class EurekaFeignClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaFeignClientApplication.class, args);
    }
}
  • 新建声明式接口,在接口上加@FeignClient注解来声明一个Feign Client,其中value为远程调用其它服务的服务名,configuration属性为Feign Client的配置类。
@FeignClient(value = "eureka-client", configuration = FeignClientConfig.class)
public interface HiFeignClient {
     // 该方法通过feign来调用eureka-client服务的“/hi”接口。
    @GetMapping("/hi")
    String hi(@RequestParam(value = "name") String name);
}


//注入feignRetryer的Bean。Feign在远程调用失败后会进行重试
@Configuration
public class FeignClientConfig {
    @Bean
    public Retryer feignRetryer() {
        return new Retryer.Default(100, SECONDS.toMillis(1), 5);
    }
}
  • 新建controller,在其中注入刚刚定义的FeignClient,并使用feignClient来调用接口。
@RestController
public class FeignController {
    @Autowired
    private HiFeignClient feignClient;

    @GetMapping("/feign-test")
    public String hi(@RequestParam String name) {
        return feignClient.hi(name);
    }
}

启动eureka-server和两个eureka-client(项目代码和启动方式见前面章节),启动eureka-feign-client。
多次访问eureka-feign-client的/feign-test接口,会轮流显示两个eureka-client的“/hi”API接口,说明Feign Client有负载均衡的能力。

FeignClient 的配置

在上文中我们通过FeignClient 的configuration 属性覆盖了Feign Client的默认配置。Feign Client默认的配置类为FeignClientsConfiguration,这个类在spring-cloud-netflix-core的jar包下,其中注入了很多Feign相关的配置,具体代码如下:

@Configuration
public class FeignClientsConfiguration {

    @Autowired
    private ObjectFactory<HttpMessageConverters> messageConverters;

    @Autowired(required = false)
    private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();

    @Autowired(required = false)
    private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList<>();

    @Autowired(required = false)
    private Logger logger;

    @Bean
    @ConditionalOnMissingBean
    public Decoder feignDecoder() {
        return new ResponseEntityDecoder(new SpringDecoder(this.messageConverters));
    }

    @Bean
    @ConditionalOnMissingBean
    public Encoder feignEncoder() {
        return new SpringEncoder(this.messageConverters);
    }

    @Bean
    @ConditionalOnMissingBean
    public Contract feignContract(ConversionService feignConversionService) {
        return new SpringMvcContract(this.parameterProcessors, feignConversionService);
    }

    @Bean
    public FormattingConversionService feignConversionService() {
        FormattingConversionService conversionService = new DefaultFormattingConversionService();
        for (FeignFormatterRegistrar feignFormatterRegistrar : feignFormatterRegistrars) {
            feignFormatterRegistrar.registerFormatters(conversionService);
        }
        return conversionService;
    }

    @Configuration
    @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
    protected static class HystrixFeignConfiguration {
        @Bean
        @Scope("prototype")
        @ConditionalOnMissingBean
        @ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = false)
        public Feign.Builder feignHystrixBuilder() {
            return HystrixFeign.builder();
        }
    }

    @Bean
    @ConditionalOnMissingBean
    public Retryer feignRetryer() {
        return Retryer.NEVER_RETRY;
    }

    @Bean
    @Scope("prototype")
    @ConditionalOnMissingBean
    public Feign.Builder feignBuilder(Retryer retryer) {
        return Feign.builder().retryer(retryer);
    }

    @Bean
    @ConditionalOnMissingBean(FeignLoggerFactory.class)
    public FeignLoggerFactory feignLoggerFactory() {
        return new DefaultFeignLoggerFactory(logger);
    }
}

@ConditionalOnMissingBean注解表示如果没有注入该类的Bean就会默认注入一个Bean,于是我们在上文中在自定义的配置类中注入Retryer 类型的Bean从而达到自定义配置的目的。

Feign 的工作原理

  • 首先通过@EnableFeignClients注解开启FeignClient的功能。只有这个注解存在,才会在程序启动时开启@FeignClient注解的包扫描。
  • 程序启动后,会进行包扫描,扫描所有的@FeignClient的注解的类,并将这些信息注入IOC容器中。
  • 当接口的方法被调用时,通过JDK的代理来生成具体的RequestTemplate模板对象。
  • 根据RequestTemplate来生成HTTP请求的Request对象。
  • Request对象交给Client去处理,其中Client的网络请求框架可以是HttpURLConnection,HttpClient和OkHttp。
  • 最后Client被封装到LoadBalanceClient类,这个类结合Ribbon做到了负载均衡。

Feign使用HttpClient或OkHttp

Feign默认使用HttpURLConnection来实现网络请求的。同时还支持HttpClient和OkHttp进行网络请求。通过FeignRibbonClientAutoConfiguration的源码:

@Configuration
    @ConditionalOnClass(ApacheHttpClient.class)
    @ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
    protected static class HttpClientFeignLoadBalancedConfiguration {

        @Autowired(required = false)
        private HttpClient httpClient;

        @Bean
        @ConditionalOnMissingBean(Client.class)
        public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
                SpringClientFactory clientFactory) {
            ApacheHttpClient delegate;
            if (this.httpClient != null) {
                delegate = new ApacheHttpClient(this.httpClient);
            }
            else {
                delegate = new ApacheHttpClient();
            }
            return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
        }
    }

    @Configuration
    @ConditionalOnClass(OkHttpClient.class)
    @ConditionalOnProperty(value = "feign.okhttp.enabled", matchIfMissing = true)
    protected static class OkHttpFeignLoadBalancedConfiguration {

        @Autowired(required = false)
        private okhttp3.OkHttpClient okHttpClient;

        @Bean
        @ConditionalOnMissingBean(Client.class)
        public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
                SpringClientFactory clientFactory) {
            OkHttpClient delegate;
            if (this.okHttpClient != null) {
                delegate = new OkHttpClient(this.okHttpClient);
            }
            else {
                delegate = new OkHttpClient();
            }
            return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
        }
    }

其中@ConditionalOnClass表示在容器中存在对应的Bean配置就会生效,@ConditionalOnProperty中属性默认为true,所以不用在application.yml配置文件中配置,只需要在pom文件加上HttpClient或者OkHttp的Classpath即可。

<dependency>
          <groupId>com.netflix.feign</groupId>
          <artifactId>feign.httpclient</artifactId>
          <version>RELEASE</version>
</dependency>
或
<dependency>
          <groupId>com.netflix.feign</groupId>
          <artifactId>feign.okhttp</artifactId>
          <version>RELEASE</version>
</dependency>

注意项

  • 由于网络原因,feign请求会超时,此时会发生异常。feign实际整合了hystrix(断路器 后续介绍),我们可以通过配置来调整超时时间。
//增加超时时间
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000 
//禁用超时
# hystrix.command.default.execution.timeout.enabled: false 
//索性禁用feign的hystrix支持 
feign.hystrix.enabled: false 
  • feign传递List<String>,被调用端会报Required List parameter 'uomIdList' is not present。此时需要传递 string[]。但传递List<Long>则可以。这是在开发中遇到的,总结不一定完全正确。
  • 不支持@GetMapping,可用@RequestMapping(value = "", method = RequestMethod.GET)来替代。在参数中@PathVariable一定得设置value。
  • 只要参数是复杂对象,即使指定了是GET方法,feign依然会以POST方法进行发送请求。这是在开发中遇到的,总结不一定完全正确。

总结

在这一章学习了如何使用Feign,并介绍了Feign的工作原理和如何切换HTTP的请求框架。最后介绍了本人在开发过程中遇到的一些坑。在下一章学习熔断器Hystrix
PS:项目github地址:https://github.com/dzydzydzy/spring-cloud-example.git

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352

推荐阅读更多精彩内容