官方说明:Feign是一个声明性web服务客户端。它使编写web服务客户机更容易。使用Feign创建一个接口并注释它。它具有可插入的注释支持,包括Feign注释和JAX-RS注释。Feign也支持可插拔编码器和解码器。
个人理解:Feign是简化了ribbon访问服务时的代码编写,使得我们能够更清楚,更方便的知道服务提供者与服务消费者之间的请求。
由于Feign已经集成了Ribbon和Hystrix,所以如果需要对他们单独进行配置,也是可以的
学习路程:
1. 怎么利用Feign简化代码
2.Feign的继承
3.Ribbon配置
4.Hystrix配置
5.其他配置
1.创建eureka注册中心:自行搭建
2.创建服务提供者:只需要注册到eureka注册中心,并写两个简单接口即可
3.创建消费者:使用Feign请求服务提供者
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
server:
port: 8090
spring:
application:
name: feign-service-provider
eureka:
client:
serviceUrl:
#配置多个eureka注册中心
defaultZone: http://localhost:9094/eureka/,http://localhost:9093/eureka/
@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包
org.springframework.cloud
spring-cloud-starter-openfeign
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
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);
}
}
//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注解,否则传参失败
@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
通过上面的案例项目,我们可以发现,服务提供方的controller层的方法其实和消费方定义的Feign接口是相同的,所以我们可以将其抽象出一个公共的接口,然后服务提供方controller层实现接口,消费方的feign接口继承即可。
这个项目是用来作依赖给服务提供方和消费方使用的,所以不需要额外的包和配置,写个接口即可。注意这里服务提供方使用的是httpclient
@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);
}
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
cn.tanfp.cloud-study
feign-api
0.0.1-SNAPSHOT
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 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;
}
}
org.springframework.cloud
spring-cloud-starter-openfeign
io.github.openfeign
feign-httpclient
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
cn.tanfp.cloud-study
feign-api
0.0.1-SNAPSHOT
@SpringBootApplication
//启动Feign
@EnableFeignClients
@EnableDiscoveryClient
public class FeignDemoApplication {
public static void main(String[] args) {
SpringApplication.run(FeignDemoApplication.class, args);
}
}
server:
port: 8091
spring:
application:
name: feign-client
eureka:
client:
serviceUrl:
#配置多个eureka注册中心
defaultZone: http://localhost:9094/eureka/,http://localhost:9093/eureka/
@FeignClient("feign-service-provider")
public interface UserServiceFeignImpl extends UserServiceApiFeign {
}
@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;
}
}
测试:启动注册中心,服务提供者,服务消费者,访问即可
我们在没有使用继承时,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)
如果传Data类型,会有时间误差
时间问题解决参考博客:https://blog.csdn.net/f641385712/article/details/82431502
第一个问题的详解:https://www.cnblogs.com/dennyzhangdd/p/7978454.html
当使用继承这种方式时,优点很明显,因为抽象了一个上层接口,所以不需要写重复代码了,但是缺点也很明显,当api中的接口改变时,项目就会报错,因为服务提供方controller层是实现的此接口,一旦该接口新增了方法,那么编译就会报错。修改的话,就要修改所有继承了该接口的controller
由于Feign的客户端负载均衡是通过Ribbon实现的,所以我们是可以自定义Ribbon的配置的。在之前的版本中,可直接使用ribbon.
例如我们修改一下客户端调用时间和连接时间为1毫秒
feign:
client:
config:
default:
connectTimeout: 1
readTimeout: 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");
}
}
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
使用Hystrix,就不能使用第二种继承的方式,因为会有很多bug,我搞了很久都没搞出来,所以我推荐,使用Hystrix时,尽量还是不要共享服务提供者和服务消费者的feign接口.
feign:
hystrix:
enabled: true
@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
@FeignClient(value = "feign-service-provider",fallback = UserServiceFeignHystrix.class)
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 1
访问接口,如果返回的是fallback中的返回值则为正确
当我们想要禁用某个服务的hystrix时,需要添加配置类:
@Configuration
public class FooConfiguration {
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder() {
return Feign.builder();
}
}
并给FeignClient配置属性
@FeignClient(name = "stores", configuration = FooConfiguration.class)
Feign支持对请求与响应进行GZIP压缩,以减少通信过程中的性能损耗,只需要配置两个参数
feign:
compression:
request:
enabled: true
# 指定只有以下类型的请求参数才会压缩
mime-types: text/xml,application/xml,application/json
#指定请求大小只有大于2048kb才会进行压缩
min-request-size: 2048
同时还能设置指定的数据类型和请求大小限制
Spring Cloud Feign在构建被@FeignClient注解修饰的服务客户端时,会为每个客户端创建一个feign.Logger实例,我们可以利用该日志对象的Debug模式来帮助分析Feign的请求细节
在application.yml中使用logging.level.
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:记录所有细节