Spring Boot 中使用Feign作为HTTP客户端调用远程HTTP服务

Feign 简介

Spring Cloud的Feign支持的一个中心概念就是命名客户端.Feign客户端使用@FeignClient注册组合成组件,按需调用远程服务器.
Spring Cloud使用FeignClientsConfiguration创建一个新的集合作为每个命名客户端的ApplicationContext(应用上下文), 包含feign.Decoder,feign.Encoder和feign.Contract.

你可以使用 Jersey 和 CXF 这些来写一个 Rest 或 SOAP 服务的java客服端。你也可以直接使用 Apache HttpClient 来实现。但是 Feign 的目的是尽量的减少资源和代码来实现和 HTTP API 的连接。通过自定义的编码解码器以及错误处理,你可以编写任何基于文本的 HTTP API。

Feign 通过注解注入一个模板化请求进行工作。只需在发送之前关闭它,参数就可以被直接的运用到模板中。然而这也限制了 Feign,只支持文本形式的API,它在响应请求等方面极大的简化了系统。同时,它也是十分容易进行单元测试的。

Spring Cloud应用在启动时,Feign会扫描标有@FeignClient注解的接口,生成代理,并注册到Spring容器中。生成代理时Feign会为每个接口方法创建一个RequetTemplate对象,该对象封装了HTTP请求需要的全部信息,请求参数名、请求方法等信息都是在这个过程中确定的,Feign的模板化就体现在这里。

maven依赖

<dependencies>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
</dependencies>

Feign客户端接口(消费者)

@FeignClient(value = "user", url = "${addr.url}")
public interface UserClient {
        //feign独有的注解方式 
        @RequestLine("GET /user/index")
        String index();

        @RequestMapping(value = "/get0/{id}", method = RequestMethod.GET)
        User findById(@PathVariable("id") Long id);

        @RequestMapping(value = "/get1", method = RequestMethod.GET)
        User get1(@RequestParam("id") Long id, @RequestParam("name") String name);

        @RequestMapping(value = "/get2", method = RequestMethod.GET)
        User get2(@RequestParam Map<String, Object> map);

        @RequestMapping(value = "/hello2", method=RequestMethod.GET)
        User hello2(@RequestHeader("name") String name, @RequestHeader("age") Integer age);

        @RequestMapping(value = "/hello3", method=RequestMethod.POST)
        String hello3(@RequestBody User user);
}

当前工程中有和Feign Client中一样的Endpoint时,Feign Client的类上不能用@RequestMapping注解否则,当前工程该endpoint http请求且使用accpet时会报404.但是,如果不包含Accept header时,请求是可以的
或者像下面不在Feign Client上使用@RequestMapping注解,请求也是可以的,无论是否包含Accept:

@FeignClient(name = "card", url = "http://localhost:7913", 
    fallback = CardFeignClientFallback.class,
    configuration = FeignClientConfiguration.class)
public interface CardFeignClient {
    @RequestMapping(value = "/v1/card/balance", method = RequestMethod.POST, 
                produces = MediaType.APPLICATION_JSON_VALUE)
    Info info();
}

Feign将方法签名中方法参数对象序列化为请求参数放到HTTP请求中的过程,是由编码器(Encoder)完成的。同理,将HTTP响应数据反序列化为java对象是由解码器(Decoder)完成的。
默认情况下,Feign会将标有@RequestParam注解的参数转换成字符串添加到URL中,将没有注解的参数通过Jackson转换成json放到请求体中。注意,如果在@RequetMapping中的method将请求方式指定为POST,那么所有未标注解的参数将会被忽略,例如:

@RequestMapping(value = "/group/{groupId}", method = RequestMethod.GET)
void update(@PathVariable("groupId") Integer groupId, 
            @RequestParam("groupName") String groupName, 
                             DataObject obj);

此时因为声明的是GET请求没有请求体,所以obj参数就会被忽略。
在Spring Cloud环境下,Feign的Encoder只会用来编码没有添加注解的参数。如果你自定义了Encoder, 那么只有在编码obj参数时才会调用你的Encoder。对于Decoder, 默认会委托给SpringMVC中的MappingJackson2HttpMessageConverter类进行解码。只有当状态码不在200 ~ 300之间时ErrorDecoder才会被调用。ErrorDecoder的作用是可以根据HTTP响应信息返回一个异常,该异常可以在调用Feign接口的地方被捕获到。我们目前就通过ErrorDecoder来使Feign接口抛出业务异常以供调用者处理

Feign在默认情况下使用的是JDK原生的URLConnection发送HTTP请求,没有连接池,但是对每个地址会保持一个长连接,即利用HTTP的persistence connection 。我们可以用Apache的HTTP Client替换Feign原始的http client, 从而获取连接池、超时时间等与性能息息相关的控制能力。Spring Cloud从Brixtion.SR5版本开始支持这种替换,首先在项目中声明Apache HTTP Client和feign-httpclient依赖:

<!-- 使用Apache HttpClient替换Feign原生httpclient -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>
        <dependency>
            <groupId>com.netflix.feign</groupId>
            <artifactId>feign-httpclient</artifactId>
            <version>${feign-httpclient}</version>
        </dependency>

然后在application.properties中添加
feign.httpclient.enabled=true

配置文件application.yml

#feign
feign:
  hystrix:
    enabled: true
  httpclient:
    enabled: true
addr:
  url: http://10.164.13.166:8080/msg-center/v1/sms/send

配置类Configuration

使用了配置@Configuration参数,自己定义Configuration类来自定义FeignClientsConfiguration,并且Configuration类的类路径不能在启动类Application的扫描路径下,否则会覆盖该项目所有的Feign接口的默认配置

package com.spring.feigin.config;

import feign.auth.BasicAuthRequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class EurekaConfiguration {
    //配置只允许使用Feign自己的注解url方式:@RequestLine
    @Bean
    public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
        return new BasicAuthRequestInterceptor("zhihao.miao", "123456");
    }
    //配置eureka的登录名和密码
    @Bean
    public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
        return new BasicAuthRequestInterceptor("zhihao.miao", "123456");
    }
}

定义主体启动类

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderApplication {
    public static void main(String[] args) {
       SpringApplication.run(OrderApplication.class,args);
    }
}

feign使用Hystrix

添加依赖
<!-- 整合hystrix,其实feign中自带了hystrix,引入该依赖主要是为了使用其中的hystrix-metrics-event-stream,用于dashboard -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
feign接口注解中,增加fallback指定类
@FeignClient (value = "${name}",  url = "${addr.url}", fallback = UserFallBack.class)
指定类中处理熔断的后续逻辑
@Slf4j
@Component
public class PosMemberClientFallBack implements PosMemberClient {
    @Override
    public String addMember(MemberDTO memberDTO) {
        log.warn("调用会员服务失败");
        return ("调用服务失败,熔断”);
    }
}
配置文件
#hystrix
hystrix:
  command:
    default:
      execution:
         isolation:
           strategy: THREAD
           thread:
             timeoutInMilliseconds: 30000
   threadpool:
     default:
       coreSize: 500 #缺省为10
修改启动类

在启动类上添加@EnableHystrix 注解

@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
@EnableHystrix
public class StartApplication {
 public static void main(String[] args) {
 SpringApplication.run(StartApplication.class, args);
 }
}

配置的fallback class也必须在FeignClient Configuration中实例化,否则会报
java.lang.IllegalStateException: No fallback instance of type class异常。

 @Configuration
    public class FooConfiguration {
        @Bean
        @Scope("prototype")
        public Feign.Builder feignBuilder() {
            return Feign.builder();
        }
        //实例化fallback
        @Bean
        public HystrixClientFallback fb(){
            return new HystrixClientFallback();
        }
    }

在Spring Cloud中,Feign和Ribbon在整合了Hystrix后,可能会出现首次调用失败的问题,要如何解决该问题呢?
Hystrix默认的超时时间是1秒,如果超过这个时间尚未响应,将会进入fallback代码。
而首次请求往往会比较慢(因为Spring的懒加载机制,要实例化一些类),这个响应时间可能就大于1秒了。
解决方案 :
方法一
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000
该配置是让Hystrix的超时时间改为5秒
方法二
hystrix.command.default.execution.timeout.enabled: false
该配置,用于禁用Hystrix的超时时间
方法三
feign.hystrix.enabled: fals
该配置,用于索性禁用feign的hystrix。该做法除非一些特殊场景,不推荐使用。

Feign的扩展配置

#Hystrix支持,如果为true,hystrix库必须在classpath中
feign.hystrix.enabled=false

#请求和响应GZIP压缩支持
feign.compression.request.enabled=true
feign.compression.response.enabled=true
#支持压缩的mime types
feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048

# 日志支持
logging.level.project.user.UserClient: DEBUG

猜你喜欢

转载自blog.csdn.net/u010541670/article/details/80068575