Fegin是什么

Feign 是一个声明式的 REST 客户端,它能让 REST 调用更加简单。Feign 供了 HTTP 请求的模板,通过编写简单的接口和插入注解,就可以定义好 HTTP 请求的参数、格式、地址等信息。而 Feign 则会完全代理 HTTP 请求,我们只需要像调用方法一样调用它就可以完成服务请求及相关处理。

Spring Cloud 对 Feign 进行了封装,使其支持 SpringMVC 标准注解和 HttpMessageConverters。Feign 可以与 Eureka 和 Ribbon 组合使用以支持负载均衡。

官方文档:https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/#spring-cloud-openfeign

springcloud fegin 调用超时 springcloud中使用fegin_spring cloud

Fegin能干什么

Feign旨在使编写java Http客户端变得更容易

前面在使用Ribbon和RestTemplate时,利用RestTemplate对请求的封装处理,形成了一套模板化的调用方法。但是在开发中,由于对服务依赖的调用可能不止一处,**往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖的调用**。所以,feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口。在**Feign实现下,我们只需要创建一个接口并使用注解的方式来配置它(以前时Dao接口上面标注Mapper注解,现在时微服务接口上标注一个Feign注解即可)**,即可完成对服务提供方的接口绑定,简化的ribbon时,自动封装服务调用客户端的开发量。

spring cloud如何使用Feign

我们创建一个新的module作为feign客户端:springcloud-consumer-user-feign-80

springcloud fegin 调用超时 springcloud中使用fegin_spring_02

一:导入依赖,代码如下所示。

<!--openfeign-->
      <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
         <!--eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>

二:启动类上加注解@EnableFeignClients

注意:如果你的 Feign 接口定义跟你的启动类不在同一个包名下,还需要制定扫描的包名 @EnableFeignClients(basePackages=“com.xxx.xxx.xxx”),代码如下所示。

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

三:使用feign接口

@Component
@FeignClient(value = "SPRINGCLOUD-PROVIDER-USER")
public interface FeignClientService {
    @PostMapping(value = "/user/add")
    int addUser(User record);
    @GetMapping(value = "/user/get/{id}")
    User selectByPrimaryKey(@PathVariable("id") Long id);
    @GetMapping(value = "/user/list")
    List<User> list();
}

服务名称:

springcloud fegin 调用超时 springcloud中使用fegin_spring_03

首先我们来看接口上加的 @FeignClient 注解。@FeignClient(value = “SPRINGCLOUD-PROVIDER-USER”)这个注解标识当前是一个 Feign 的客户端,value 属性是对应的服务名称,也就是你需要调用哪个服务中的接口。selectByPrimaryKey()方法中@GetMapping(value = “/user/get/{id}”)和服务提供者8001的暴露的接口路径是一样的。(必须一样,否则无法访问到)

如我的服务提供者的暴露的接口时这样的:springcloud-provider-user-8001,所以上述的接口访问路径也要一致,但方法名可以随便取

//提供restful服务
@RestController
@RequestMapping
public class UserController {
    @Autowired
    private UserService userService;
    @PostMapping("/user/add")
    public int addUser(User user){
        return userService.insert(user);
    }
    @GetMapping("/user/get/{id}")
    public User get(@PathVariable("id")Long id){
        return userService.selectByPrimaryKey(id);
    }
    @GetMapping("/user/list")
    public List<User> list(){
        return userService.queryAll();
    }
    }

定义方法时直接复制接口的定义即可,当然还有另一种做法,就是将接口单独抽出来定义,然后在 Controller 中实现接口。

在调用的客户端中也实现了接口,从而达到接口共用的目的。我这里的做法是不共用的,即单独创建一个 API Client 的公共项目,基于约定的模式,每写一个接口就要对应写一个调用的 Client,后面打成公共的 jar,这样无论是哪个项目需要调用接口,只要引入公共的接口 SDK jar 即可,不用重新定义一遍了。

定义之后可以直接通过注入 UserConsumerController 来调用,这对于开发人员来说就像调用本地方法一样。

接下来采用 Feign 来调用 这些接口,代码如下所示。

@RestController
public class UserConsumerController {
    @Autowired
    private FeignClientService feignClientService;
    @GetMapping("/consumer/user/get/{id}")
    public User get(@PathVariable("id")Long id){
     return this.feignClientService.selectByPrimaryKey(id);
    }
    @PostMapping("/consumer/user/add")
    public int addUser(User user){
        return this.feignClientService.addUser(user);
    }
    @GetMapping("/consumer/user/list")
    public List<User> list(){
        return this.feignClientService.list();
    }
}

通过Ribbon的代码相比可以发现,我们的调用方式变得越来越简单了,从最开始的指定地址,到后面通过 Eureka 中的服务名称来调用,再到现在直接通过定义接口来调用。

这是Ribbon形式的代码,我们可以与上面相比较是不是真正符合了面向接口编程

@RestController
public class UserConsumerController {
    @Autowired
    private RestTemplate restTemplate;//提供多种便捷访问远程http服务的方法,简单的restful服务模板
    private static final String REST_URL_PREFIX="http://springcloud-provider-user";
    @RequestMapping("/consumer/user/get/{id}")
    public User get(@PathVariable("id")Long id){
     return restTemplate.getForObject(REST_URL_PREFIX+"/user/get/"+id, User.class);//注意get还是post
    }
    @RequestMapping("/consumer/user/add")
    public int addUser(User user){
        return restTemplate.postForObject(REST_URL_PREFIX+"/user/add",user,int.class);
    }
    @RequestMapping("/consumer/user/list")
    public List<User> list(){
        return restTemplate.getForObject(REST_URL_PREFIX+"/user/list",List.class);
    }
}

最后是application.yml配置

server:
  port: 80
eureka:
  client:
    register-with-eureka: false #不向eureka注册自己
   service-url:
     #eureka集群 
     defaultZone: http://localhost:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
Feign超时控制

Feign调用服务的默认时长是1秒钟,也就是如果超过1秒没连接上或者超过1秒没响应,那么会相应的报错。而实际情况是因为业务的不同可能出现超出1秒的情况,这时我们需要调整超时时间。   全局配置

因为Feign 的负载均衡底层用的就是 Ribbon   在application.properties中添加如下配置,超过5秒没连接上报连接超时,如果超过5秒没有响应,报请求超时

ribbon:
  #指的是建立连接所用的时间,适用于网络状态正常情况下,两端连接所用的时间
  ReadTimeOut: 5000
  #建立连接后从服务器读取到可用资源所用时间
  ConnectTimeout: 5000

演示: 我们故意在服务提供者 springcloud-provider-user-8001编写一个方法,让他睡眠三秒,起到连接超时的效果,(注意:没有加上述的配置)

@GetMapping("/user/feign/timeout")
    public String UserFeignTimeout(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "服务超时";
    }

客户端获取连接 :springcloud-consumer-user-feign-80

public interface FeignClientService{} 接口中增加方法:

@GetMapping("/user/feign/timeout")
    String UserFeignTimeout();

UserConsumerController中

/**
     * 模拟feign超时,因为feign接受请求连接的时间默认是1秒
     */
    @GetMapping("/consumer/user/feign/timeout")
    public String UserFeignTimeout(){
        return this.feignClientService.UserFeignTimeout();
    }

客户端超时连接则会出现如下:

springcloud fegin 调用超时 springcloud中使用fegin_java_04

springcloud fegin 调用超时 springcloud中使用fegin_User_05

所以,为了保证正常连接我们一般会设置上述的超时时间。

局部配置 当然我们也可以不使用全局配置,使用feign的局部配置也是一样的。

feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 5000

当我们设置了超时时间时,在进行访问就能正常访问了。

日志配置

有时候我们遇到 Bug,比如接口调用失败、参数没收到等问题,或者想看看调用性能,就需要配置 Feign 的日志了,以此让 Feign 把请求信息输出来。

首先定义一个配置类,代码如下所示。

/**
     * 配置Feign日志功能,配置日志,日志级别
     * 注意包:import feign.Logger
     */
    @Bean
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }

通过源码可以看到日志等级有 4 种,分别是:

NONE:不输出日志。

BASIC:只输出请求方法的 URL 和响应的状态码以及接口执行的时间。

HEADERS:将 BASIC 信息和请求头信息输出。

FULL:输出完整的请求信息。


Feign 日志等级源码如下所示:

public enum Level {
    NONE,
    BASIC,
    HEADERS,
    FULL
}

配置application.yml

#日志
#feign.client.config.feignName.loggerLevel=full
logging:
  level:
    #feign日志以什么级别监控哪个接口
    com.huang.springcloud.service.FeignClientService: debug

开启日志之后,我们访问客户端某个接口的时候就能清楚的看到一些http请求的详细信息了,如图

springcloud fegin 调用超时 springcloud中使用fegin_spring cloud_06

如果你感兴趣可以继续学习:springcloud(五) --hystrix服务降级熔断的简单介绍及使用