Feign入门

Feign入门

在前面的学习中,我们使用了Ribbon的负载均衡,大大的简化了远程调用过程中的代码:

    private static final String REST_URL_PREFIX = "http://emp-provider";

    @GetMapping("/{id}")
    public String getEmployeeById(@PathVariable Integer id) {
        String url = REST_URL_PREFIX + "/employee/" + id;
        //调用接口
        String employee = restTemplate.getForObject(url, String.class);
        //返回结果
        return employee;
    }

但是代码中仍然存在着大量重复的代码,代码格式基本相同,无非是参数不一样,接下来学习的Feign就能解决这个问题。

Feigin,英文译为伪装,它可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。你不用再自己拼接url,拼接参数等等操作,一切都交给Feign去做。

简介

Feign是Netlix开发的声明式、模板化的HTTP客户端,其灵感来自Retrofit、 JAXRS-2.0以及WebSocket。Feign 可帮助我们更加便捷、优雅地调用HTTP API。
在Spring Cloud中,使用Feign非常简单一创建- -个接口, 并在接口上添加一些注解,代码就完成了。Feign 支持多种注解,例如Feign自带的注解或者JAX-RS注解等。
Spring Cloud对Feign进行了增强,使Feign支持了Spring MVC注解,并整合了Ribbon和Eureka,从而让Feign的使用更加方便。

Feign的基本使用

使用Feign我们要告诉它几个条件:请求方式、请求路径、请求参数、返回结果。而这几个条件都可以通过SpringMVC的注解来告诉它

第一步:引入依赖,在新版本的SpringCloud中,我们都使用的是openfeign的starter

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

第二步:加注解,在启动器上加上@EnableFeignClients注解

package cn.rayfoo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/2 2:27 下午
 * @Description:
 */
@EnableFeignClients
@SpringCloudApplication
public class CustomerRunner {

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

}

第三步:编写服务调用接口,告诉它请求方式、请求路径、请求参数、返回结果。

package cn.rayfoo.client;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/4 4:03 下午
 * @Description:
 */
@FeignClient("emp-provider")
public interface EmployeeClient {

    @GetMapping("/employee/{id}")
    String getEmployeeById(@PathVariable Integer id);

}

此时,就可以通过这个EmployeeClient的getEmployeeById方法就会走动从Eureka中拉取FeignClient注解提供服务名的服列表,自动由Ribbon完成负载均衡,去挑选任意一个服务来执行,一切自动完成。我们只需要直接注入此接口,像调用本地方法一样调用服务。

前面RestTemplateConfiguration的配置类也可以去除

package cn.rayfoo.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/2 2:29 下午
 * @Description:
 */
@Configuration
public class RestTemplateConfiguration {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

}

此时controller中也无需注入restTemplate,取而代之的是EmployeeClient

package cn.rayfoo.controller;

import cn.rayfoo.client.EmployeeClient;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/2 2:30 下午
 * @Description:
 */
@RestController
@RequestMapping("/employee")
@DefaultProperties(defaultFallback = "defaultFallbackMethod")
public class EmployeeController {


    @Autowired
    private EmployeeClient employeeClient;


    @GetMapping("/{id}")
    @HystrixCommand(commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "2500"),
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "40")
    })
    public String getEmployeeById(@PathVariable Integer id) {
        //调用接口
        String employee = employeeClient.getEmployeeById(id);
        //返回结果
        return employee;
    }

    public String defaultFallbackMethod() {
        //返回结果
        return "服务器繁忙, 请稍后重试!";
    }

}

这样Feign就通过底层的动态代理帮我们完成了一次服务的调用。(自动完成了负载均衡、服务降级、服务熔断)

引入了Feign后,Ribbon的依赖都无需引入,因为Feign中引入了Ribbon,

Ribbon超时时长配置

ribbon:
  ConnectionTimeOut: 500
  ReadTimeOut: 5000

其中ConnectionTimeOut为,多少毫秒内没有获取到服务连接,就抛出异常

ReadTimeOut为获取到连接了,但是多少毫秒内没有拿到数据,抛出异常

在使用Feign的时候,建议加入如下配置。

Feign配置Hystrix熔断

Feign中配置Hystrix的方法就有了些许改变。

1、首先要配置开启Hystrix

feign:
  hystrix:
    enabled: true

2、在FeignClient注解上配置fallback属性,指定一个类,在这个类上配置Feign的熔断配置。这个类必须实现EmployeeClient接口。

package cn.rayfoo.client;

import cn.rayfoo.client.impl.EmployeeClientFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/4 4:03 下午
 * @Description:
 */
@FeignClient(value = "emp-provider",fallback = EmployeeClientFallback.class)
public interface EmployeeClient {

    @GetMapping("/employee/{id}")
    String getEmployeeById(@PathVariable Integer id);

}

3、在实现类上,实现熔断的业务逻辑

package cn.rayfoo.client.impl;

import cn.rayfoo.client.EmployeeClient;
import org.springframework.stereotype.Component;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/4 5:36 下午
 * @Description:
 */
@Component
public class EmployeeClientFallback implements EmployeeClient {
    @Override
    public String getEmployeeById(Integer id) {
        return "服务器繁忙,请稍后重试";
    }
}

Hystrix请求压缩

Spring Cloud Feign 支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。通过下面的参数即可开启请求与响应的压缩功能:

feign:
  compression:
    request:
      enabled: true # 开启请求压缩
    response:
      enabled: true # 开启响应压缩

同时,我们也可以对请求的数据类型,以及触发压缩的大小下限进行设置:

feign:
  compression:
    request:
      enabled: true # 开启请求压缩
      mime-types: text/html,application/xml,application/json # 设置压缩的数据类型
      min-request-size: 2048 # 设置触发压缩的大小下限

注:上面的数据类型、压缩大小下限均为默认值。

Feign整合Hystrix

Feign一般用在服务消费者一方,下面是具体的使用代码:

pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SpringCloudParent</artifactId>
        <groupId>cn.rayfoo</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>emp-consumer</artifactId>
    <packaging>jar</packaging>

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

</project>

配置yml文件

server:
  port: 8082
spring:
  application:
    name: emp-customer
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka,http://127.0.0.1:8762/eureka
ribbon:
  ConnectionTimeOut: 500
  ReadTimeOut: 5000
feign:
  hystrix:
    enabled: true
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000

JavaBean

package cn.rayfoo.bean;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/1 8:52 下午
 * @Description:
 */
public class Employee implements Serializable {

    private Integer empno;
    private String ename;
    private String job;
    private String mgr;
    private Date hiredate;
    private BigDecimal salary;
    private Double comm;
    private Integer deptno;

    public Integer getEmpno() {
        return empno;
    }

    public void setEmpno(Integer empno) {
        this.empno = empno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    public String getMgr() {
        return mgr;
    }

    public void setMgr(String mgr) {
        this.mgr = mgr;
    }

    public Date getHiredate() {
        return hiredate;
    }

    public void setHiredate(Date hiredate) {
        this.hiredate = hiredate;
    }

    public BigDecimal getSalary() {
        return salary;
    }

    public void setSalary(BigDecimal salary) {
        this.salary = salary;
    }

    public Double getComm() {
        return comm;
    }

    public void setComm(Double comm) {
        this.comm = comm;
    }

    public Integer getDeptno() {
        return deptno;
    }

    public void setDeptno(Integer deptno) {
        this.deptno = deptno;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "empno=" + empno +
                ", ename='" + ename + '\'' +
                ", job='" + job + '\'' +
                ", mgr='" + mgr + '\'' +
                ", hiredate=" + hiredate +
                ", salary=" + salary +
                ", comm=" + comm +
                ", deptno=" + deptno +
                '}';
    }

    public Employee() {
    }
}

client以及clientFallback

package cn.rayfoo.client;

import cn.rayfoo.client.impl.EmployeeClientFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/4 4:03 下午
 * @Description:
 */
@FeignClient(value = "emp-provider",fallback = EmployeeClientFallback.class)
public interface EmployeeClient {

    @GetMapping("/employee/{id}")
    String getEmployeeById(@PathVariable Integer id);

}

package cn.rayfoo.client.impl;

import cn.rayfoo.client.EmployeeClient;
import org.springframework.stereotype.Component;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/4 5:36 下午
 * @Description:
 */
@Component
public class EmployeeClientFallback implements EmployeeClient {
    @Override
    public String getEmployeeById(Integer id) {
        return "服务器繁忙,请稍后重试";
    }
}

controller

package cn.rayfoo.controller;

import cn.rayfoo.client.EmployeeClient;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/2 2:30 下午
 * @Description:
 */
@RestController
@RequestMapping("/employee")
public class EmployeeController {

    @Autowired
    private EmployeeClient employeeClient;

    @GetMapping("/{id}")
    public String getEmployeeById(@PathVariable Integer id) {
        //String url = REST_URL_PREFIX + "/employee/" + id;
        //调用接口
        String employee = employeeClient.getEmployeeById(id);
        //返回结果
        return employee;
    }

}

启动器:

package cn.rayfoo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/2 2:27 下午
 * @Description:
 */
@EnableFeignClients
@SpringCloudApplication
public class CustomerRunner {

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

}

posted @ 2020-06-23 18:29  张瑞丰  阅读(453)  评论(0编辑  收藏  举报