spring cloud feign的学习与使用

说明

官方说明:Feign是一个声明性web服务客户端。它使编写web服务客户机更容易。使用Feign创建一个接口并注释它。它具有可插入的注释支持,包括Feign注释和JAX-RS注释。Feign也支持可插拔编码器和解码器。

个人理解:Feign是简化了ribbon访问服务时的代码编写,使得我们能够更清楚,更方便的知道服务提供者与服务消费者之间的请求。

由于Feign已经集成了Ribbon和Hystrix,所以如果需要对他们单独进行配置,也是可以的

OpenFeign是Spring Cloud在Feign的基础上支持了SpringMVC的注解如@RequesMapping等等。OpenFeign的@ FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

所以本文中存在的springmvc的注解问题都可以忽略,直接引用OpenFeign包就行了

学习路程:

1. 怎么利用Feign简化代码

2.Feign的继承

3.Ribbon配置

4.Hystrix配置

5.其他配置

1.利用Feign简化代码

1.创建eureka注册中心:自行搭建

2.创建服务提供者:只需要注册到eureka注册中心,并写两个简单接口即可

3.创建消费者:使用Feign请求服务提供者

服务提供者

引入依赖

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

启动类添加注解@EnableDiscoveryClient

application.yml

server:
  port: 8090
spring:
  application:
    name: feign-service-provider

eureka:
  client:
    serviceUrl:
      #配置多个eureka注册中心
      defaultZone: http://localhost:9094/eureka/,http://localhost:9093/eureka/

controller接口

@RestController
public class ServiceProviderController {

    @Value("${server.port}")
    private Integer port;

    @PostMapping("/greeting")
    public String greeting(@RequestBody User user) {
        return "Greeting , " + user + " on port : " + port;
    }

    @GetMapping("/ok")
    public String ok(String msg) {
        return "用户接口:"+msg;
    }
}

服务消费者

由于Feign默认使用了ribbon和Hystrix,所以不需要单独再引入ribbon包

引入依赖

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

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

application.yml

server:
  port: 8091
spring:
  application:
    name: feign-client

eureka:
  client:
    serviceUrl:
      #配置多个eureka注册中心
      defaultZone: http://localhost:9094/eureka/,http://localhost:9093/eureka/

启动类上添加注解

@SpringBootApplication
//启动Feign
@EnableFeignClients
@EnableDiscoveryClient
public class FeignDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(FeignDemoApplication.class, args);
    }

}

新建一个UserServiceFeign接口用来请求服务提供者

//feign-service-provider表示服务提供者的应用名
@FeignClient("feign-service-provider")
public interface UserServiceFeign {

    @GetMapping("/ok")
    String okMsg (@RequestParam  String msg);

    @PostMapping("/greeting")
    String greeting(@RequestBody  User user);

    @GetMapping("/okuser")
    String okUser(@SpringQueryMap User user);

    @PostMapping("/okuserpost")
    String okUserPost (@SpringQueryMap User user);
}

当调用UserServiceFeign中的方法时,会自动使用restTemplate拼接url,例如调用okMsg方法:feign-service-provider/ok

注意:方法中的参数都需要使用@RequestParam或者@RequestBody等注解来指定你传入的参数形式,否则不能正确传入参数,这里和在controller中是有区别的。

当参数是一个Form Data或者Query Param对象(不包括String)时,不能使用@RequestParam,或者不填,必须要使用@SpringQueryMap注解,否则传参失败

controller请求接口

@RestController
public class Controller {

    @Autowired
    private UserServiceFeign userServiceFeign;

    @GetMapping("/ok")
    public String okMsg() {
        String s = userServiceFeign.okMsg("haha");
        return "client"+ s;
    }

    @PostMapping("/greet")
    public String greeting () {
        User user = new User();
        user.setId(2L);
        user.setName("tanfp");
        String greeting = userServiceFeign.greeting(user);
        return "client"+ greeting;
    }

    @GetMapping("okuser")
    public String okUser () {
        User user = new User();
        user.setId(3L);
        user.setName("tanfp3");
        String s = userServiceFeign.okUser(user);
        return "client"+ s;
    }

    @GetMapping("okuserpost")
    public String okUserPost () {
        User user = new User();
        user.setId(4L);
        user.setName("tanfp4");
        String s = userServiceFeign.okUserPost(user);
        return "client"+ s;
    }
}

测试

启动eureka注册中心和服务消费者,服务提供者,访问http://localhost:8091/okuserpost

2.Feign的继承

通过上面的案例项目,我们可以发现,服务提供方的controller层的方法其实和消费方定义的Feign接口是相同的,所以我们可以将其抽象出一个公共的接口,然后服务提供方controller层实现接口,消费方的feign接口继承即可。

公共api接口

这个项目是用来作依赖给服务提供方和消费方使用的,所以不需要额外的包和配置,写个接口即可。注意这里服务提供方使用的是httpclient

FeignService:

@RequestMapping("/feign")
public interface UserServiceApiFeign {


    @GetMapping("/ok")
    String okMsg(@RequestParam String msg);

    @PostMapping(value = "/greeting")
    String greeting(@RequestBody User user);

    // 由于使用了http client,可以将@RequestBody作用于get请求,来传对象
    @GetMapping("/okuser")
    String okUser(@RequestBody User user);

    // 由于使用了http client,为了区分post请求的参数是from-data还是json又或者是x-www-form-urlencoded
    // 需要通过consumes属性来指定,当使用json传参时,只需要加个@RequestBody即可,如果是其他两种
    // 只能通过consumes属性指定
    @PostMapping(value = "/okuserpost", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    String okUserPost( User user);
}

服务提供方

引入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>cn.tanfp.cloud-study</groupId>
    <artifactId>feign-api</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

application.yml不变

server:
  port: 8090
spring:
  application:
    name: feign-service-provider

eureka:
  client:
    serviceUrl:
      #配置多个eureka注册中心
      defaultZone: http://localhost:9094/eureka/,http://localhost:9093/eureka/

启动类添加@EnableDiscoveryClient注解

controller层

//一定要加上该注解,表明这是controller对象
@RestController
public class FeignProviderController implements UserServiceApiFeign {
    @Override
    public String okMsg(String msg) {
        return "用户接口:"+msg;
    }

    @Override
    public String greeting(User user) {
        return "Greeting , " + user;
    }

    @Override
    public String okUser(User user) {
        return "用户接口:"+user;
    }

    @Override
    public String okUserPost(User user) {
        return "用户接口:"+user;
    }
}

服务消费者

引入依赖

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

<!--解决feign使用get请求不能使用request body的bug-->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>

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

<dependency>
    <groupId>cn.tanfp.cloud-study</groupId>
    <artifactId>feign-api</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

启动类

@SpringBootApplication
//启动Feign
@EnableFeignClients
@EnableDiscoveryClient
public class FeignDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(FeignDemoApplication.class, args);
    }

}

application.yml

server:
  port: 8091
spring:
  application:
    name: feign-client

eureka:
  client:
    serviceUrl:
      #配置多个eureka注册中心
      defaultZone: http://localhost:9094/eureka/,http://localhost:9093/eureka/

创建用来访问服务提供方的feign接口service

@FeignClient("feign-service-provider")
public interface UserServiceFeignImpl extends UserServiceApiFeign {
}

controller层不变

@RestController
public class Controller {

    @Autowired
    private UserServiceFeignImpl userServiceFeign;

    @GetMapping("/ok")
    public String okMsg() {
        String s = userServiceFeign.okMsg("haha");
        return "client"+ s;
    }

    @PostMapping("/greet")
    public String greeting () {
        User user = new User();
        user.setId(2L);
        user.setName("tanfp");
        String greeting = userServiceFeign.greeting(user);
        return "client"+ greeting;
    }

    @GetMapping("okuser")
    public String okUser () {
        User user = new User();
        user.setId(3L);
        user.setName("tanfp3");
        String s = userServiceFeign.okUser(user);
        return "client"+ s;
    }

    @GetMapping("okuserpost")
    public String okUserPost () {
        User user = new User();
        user.setId(4L);
        user.setName("tanfp4");
        String s = userServiceFeign.okUserPost(user);
        return "client"+ s;
    }
}

测试:启动注册中心,服务提供者,服务消费者,访问即可

遇到的问题

1. 请求参数类型问题

我们在没有使用继承时,feign接口如果想要传递get或post请求的from data类型且参数是一个对象时,可使用@SpringQueryMap注解,但是我们这里是将这个公共接口提取到单独的一个公共项目中了,而其中没有引入feign的包,不能使用@SpringQueryMap注解,而且又必须要有值,比如你使用get请求传一个对像,如果你这个对象前不写注解或者写@RequestBody,@RequestParam都是会报错的,错误就是:Post请求方式不支持

因为在Feign源码中,如果request body中没有内容时,默认会切换成post请求去调接口。

所以这样的话服务消费者在继承该接口并调用时就势必会报错,那么这里怎么解决呢?有两种办法:

第一种:将对象的各个属性拆分出来后使用@RequestParam注解

第二种:使用feign-httpclient包,即将请求实现换成http-client,spring 默认使用的是HttpURLConnection。引入http-client后,使用方法会有略微不同:

   1:当使用的get请求,然后传参是一个对象是,直接加上@RequestBody注解即可成功(使用默认的HttpURLConnection这样做是会报错的)

   2:当使用post请求,传参为json时,和原来的一样,使用@RequestBody即可

   3:当使用post请求,传参不为json时,此时需要使用使用@RequestMapping中的consumes属性来指定类型,如我想使用form-data类型:

@PostMapping(value = "/okuserpost", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)

使用x-www-form-urlencoded类型:

@PostMapping(value = "/okuserpost", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)

2. 时间参数问题

如果传Data类型,会有时间误差

3. bug解决

时间问题解决参考博客:【小家Spring】Feign发送Get请求时,采用POJO对象传递参数的最终解决方案 Request method ‘POST‘ not supported (附带其余好几个坑)_feignformatterregistrar 不生效_YourBatman的博客-CSDN博客

第一个问题的详解:https://www.cnblogs.com/dennyzhangdd/p/7978454.html

4. 不足

当使用继承这种方式时,优点很明显,因为抽象了一个上层接口,所以不需要写重复代码了,但是缺点也很明显,当api中的接口改变时,项目就会报错,因为服务提供方controller层是实现的此接口,一旦该接口新增了方法,那么编译就会报错。修改的话,就要修改所有继承了该接口的controller

3. Ribbon配置

由于Feign的客户端负载均衡是通过Ribbon实现的,所以我们是可以自定义Ribbon的配置的。在之前的版本中,可直接使用ribbon.<key>=<value>的形式来配置,但是这个版本是不行的,需要使用feign.client.xxxxx来配置

全局配置

例如我们修改一下客户端调用时间和连接时间为1毫秒

feign:
  client:
    config:
      default:
        connectTimeout: 1
        readTimeout: 1

指定服务配置

1.使用接口的形式(官网的例子)

@FeignClient(name = "stores", configuration = FooConfiguration.class)
public interface StoreClient {
    //..
}
@Configuration
public class FooConfiguration {
    @Bean
    public Contract feignContract() {
        return new feign.Contract.Default();
    }

    @Bean
    public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
        return new BasicAuthRequestInterceptor("user", "password");
    }
}

2. 使用配置的方式(官网的例子)

feign:
  client:
    config:
      feignName:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: full
        errorDecoder: com.example.SimpleErrorDecoder
        retryer: com.example.SimpleRetryer
        requestInterceptors:
          - com.example.FooRequestInterceptor
          - com.example.BarRequestInterceptor
        decode404: false
        encoder: com.example.SimpleEncoder
        decoder: com.example.SimpleDecoder
        contract: com.example.SimpleContract

4. Hystrix配置

使用Hystrix,就不能使用第二种继承的方式,因为会有很多bug,我搞了很久都没搞出来,所以我推荐,使用Hystrix时,尽量还是不要共享服务提供者和服务消费者的feign接口.

通过配置开启hystrix(默认是关闭的)

feign:
  hystrix:
    enabled: true

写一个服务降级的实现类,实现feign接口

@Component
public class UserServiceFeignHystrix implements UserServiceFeign {
    @Override
    public String okMsg(String msg) {
        return "okMsg";
    }

    @Override
    public String greeting(User user) {
        return "greeting";
    }

    @Override
    public String okUser(User user) {
        return  "okUser";
    }

    @Override
    public String okUserPost(User user) {
        return "okUserPost";
    }
}

注意:使用@Component注解,否则调用时会报错:No fallback instance of type class xxx found for feign client xxx

在feign接口上添加fallback属性

@FeignClient(value = "feign-service-provider",fallback = UserServiceFeignHystrix.class)

为了测试,我们将hystrix的超时时间设为1ms

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 1

测试接口

访问接口,如果返回的是fallback中的返回值则为正确

禁用某个服务的hystrix(官网例子)

当我们想要禁用某个服务的hystrix时,需要添加配置类:

@Configuration
public class FooConfiguration {
        @Bean
    @Scope("prototype")
    public Feign.Builder feignBuilder() {
        return Feign.builder();
    }
}

 并给FeignClient配置属性

@FeignClient(name = "stores", configuration = FooConfiguration.class)

5.其他配置

5.1请求压缩

Feign支持对请求与响应进行GZIP压缩,以减少通信过程中的性能损耗,只需要配置两个参数

feign:
  compression:
    request:
      enabled: true
      # 指定只有以下类型的请求参数才会压缩
      mime-types: text/xml,application/xml,application/json
      #指定请求大小只有大于2048kb才会进行压缩
      min-request-size: 2048

同时还能设置指定的数据类型和请求大小限制

5.2 日志配置

Spring Cloud Feign在构建被@FeignClient注解修饰的服务客户端时,会为每个客户端创建一个feign.Logger实例,我们可以利用该日志对象的Debug模式来帮助分析Feign的请求细节

开启日志

在application.yml中使用logging.level.<FeignClient>的配置来开启日志,其中<FeignClient>为Feign客户端定义接口的完整路径,如:

logging:
  level:
    cn.tanfp.cloudstudy.feigndemo.service.UserServiceFeign: debug

但是,只是添加上面的配置,还无法实现日志输出,因为Feign客户端默认的Logger.level对象定义为None级别,该级别不会记录任何调用过程中的信息,我们需要修改它的几倍,针对全局的日志级别,可在主应用中直接加入Logger.level的Bean创建,如:

@SpringBootApplication
//启动Feign
@EnableFeignClients
@EnableDiscoveryClient
public class FeignDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(FeignDemoApplication.class, args);
    }

    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

当然也可以通过实现配置类,然后再具体的Feign客户端来指定配置类以实现是否要调整不同的日志级别:具体实现如下:

@Configuration
public class FooConfiguration {

    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}
@FeignClient(name = "stores", configuration = FooConfiguration.class)

Feign的Logger级别主要有4种:

NONE: 不记录任何信息(默认)

BASIC: 仅记录请求方法,url和响应状态码和执行时间

HEADERS: 除了记录Basic的信息外,还会记录请求和响应头信息

FULL:记录所有细节

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巴中第一皇子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值