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);
}
}