目录
一、前言
Feign 是 Netflix 公司发布的一种实现负载均衡和服务调用的开源组件。Spring Cloud 将其与 Netflix 中的其他开源服务组件(例如 Eureka、Ribbon 以及 Hystrix 等)一起整合进 Spring Cloud Netflix 模块中,整合后全称为 Spring Cloud Netflix Feign。
Feign 对 Ribbon 进行了集成,利用 Ribbon 维护了一份可用服务清单,并通过 Ribbon 实现了客户端的负载均衡。
Feign 是一种声明式服务调用组件
,它在 RestTemplate
的基础上做了进一步的封装。通过 Feign,我们只需要声明一个接口并通过注解进行简单的配置(类似于 Dao 接口上面的 Mapper 注解一样)即可实现对 HTTP 接口的绑定。
Feign 支持多种注解,例如 Feign 自带的注解以及 JAX-RS 注解等,但遗憾的是 Feign 本身并不支持 Spring MVC 注解,这无疑会给广大 Spring 用户带来不便。
2019 年 Netflix 公司宣布 Feign 组件正式进入停更维护状态,于是 Spring 官方便推出了一个名为 OpenFeign
的组件作为 Feign 的替代方案。
二、openFeign简介
Open Feign是Spring Cloud的二级子项目。
OpenFeign是一种声明式、模板化的HTTP客户端
(仅在Application Client中使用)(称OpenFeign作用:声明式服务调用
)。
声明式调用是指,就像调用本地方法一样调用远程方法,无需感知操作远程http请求。
Spring Cloud的声明式调用, 可以做到使用 HTTP请求远程服务时能就像调用本地方法一样
的体验,开发者完全感知不到这是远程方法,更感知不到这是个HTTP请求。
Feign的应用,让Spring Cloud微服务调用像Dubbo一样,Application Client直接通过接口方法调用Application Service,而不需要通过常规的RestTemplate构造请求再解析返回数据
。它解决了让开发者调用远程接口就跟调用本地方法一样,无需关注与远程的交互细节,更无需关注分布式环境开发。
使用OpenFeign时就好像在写控制器方法,OpenFeign都是写在接口中,在声明的方法上添加SpringMVC注解或声明的参数上添加SpringMVC注解就可以完成调用远程的控制器方法。
Feign 和 OpenFegin的区别:
相同点:
(1) Feign 和 OpenFeign 都是 Spring Cloud 下的远程调用和负载均衡组件;
(2) Feign 和 OpenFeign 作用一样,都可以实现服务的远程调用和负载均衡;
(3) Feign 和 OpenFeign 都对 Ribbon 进行了集成,都利用 Ribbon 维护了可用服务清单,并通过 Ribbon 实现了客户端的负载均衡;
(4) Feign 和 OpenFeign 都是在服务消费者(客户端)
定义服务绑定接口并通过注解的方式进行配置,以实现远程服务的调用;
不同点:
(1) Feign 和 OpenFeign 的依赖项不同,Feign 的依赖为 spring-cloud-starter-feign,而OpenFeign 的依赖为 spring-cloud-starter-openfeign;
(2) Feign 和 OpenFeign 支持的注解不同,Feign 支持 Feign 注解和 JAX-RS 注解,但不支持 Spring MVC 注解;
(3)OpenFeign 除了支持 Feign 注解和 JAX-RS 注解外,还支持 Spring MVC 注解
;
三、使用OpenFeign时的程序执行流程(原理)
OpenFeign代替之前的RestTemplate代码。也是写在Application Client中
。把OpenFeign接口单独放在feign包中,表示服务调用层。当需要调用其他服务时,直接注入OpenFeign接口对象就可以像调用本地方法一样调用远程服务
。
整体流程说明:
-
ApplicationService 向Eureka Server 注册服务。
-
Application Client从Eureka Server中发现服务信息,获取服务列表。
-
在Application Client中调用OpenFeign接口中方法
-
Application Client中OpenFeign通过
应用程序名
调用Application Service
四、Feign的应用
1.搭建SpringCloud服务注册中心
创建Springboot项目,添加如下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
配置application.properties:
## 端口号
server.port=8083
## eureka主机名
eureka.instance.hostname=localhost
## 指定当前主机是否需要向注册中心注册(不用,因为当前主机是Server,不是client)
eureka.client.register-with-eureka=false
## 指定当前主机是否需要获取注册信息(不用,因为当前主机是Server,不是client)
eureka.client.fetch-registry=false
## 注册中心地址
eureka.client.service-url.defaultZone=http://${
eureka.instance.hostname}:${
server.port}/eureka
配置启动类:
在项目启动类上面使用@EnableEurekaServer
,可以将项目作为Spring Cloud的注册中心(启动 Eureka 服务注册中心)
服务注册中心就搭建好了
2.新建Application Service项目(服务提供方)
2.1 添加依赖
添加了web环境和eureka client依赖
<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>
2.2 编写配置文件
必须要有应用程序名,因为OpenFeign是通过应用程序名
进行调用。
## 端口号
server.port=8081
## 服务名称
spring.application.name=service-demo
## 将服务注册到注册中心 eureka_service 的地址
eureka.client.service-url.defaultZone=http://localhost:8083/eureka/
2.3 新建控制器
controller:
/**
* @author qzz
*/
@RestController
public class DemoController {
@Autowired
private DemoService demoService;
/**
* 无参请求
* @return
*/
@RequestMapping("/demo")
public String demo(){
return demoService.demo();
}
}
serviceImpl:
/**
* @author qzz
*/
@Service
public class DemoServiceImpl implements DemoService {
@Override
public String demo() {
return "demo";
}
}
2.4 配置启动类
/**
* @author qzz
*/
@SpringBootApplication
public class ServiceDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceDemoApplication.class, args);
}
}
3.新建Application Client项目(服务调用方)
3.1 添加依赖
比application service项目多了openfeign的依赖
<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-openfeign</artifactId>
</dependency>
3.2 编写配置文件
## 端口号
server.port=8085
## 服务名称
spring.application.name=openfeign-demo
## 将服务注册到注册中心 eureka_service 的地址
eureka.client.service-url.defaultZone=http://localhost:8083/eureka/
3.3 新建OpenFeign接口
OpenFeign接口命名:调用应用程序+Feign
package com.example.openfeigndemo.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @FeignClient():定义feign客户端
* service-demo客户端
* @author qzz
*/
@FeignClient(name = "service-demo")
public interface ServiceDemoFeign {
/**
* 方法要求:
* 返回值:要对应
* 方法名:随意
* 参数:要对应
* 方法上添加SpringMvc注解
* @return
*/
@RequestMapping("/demo")
String suiyi();
}
注意:
@FeignClient 参数要写调用的Application Service的应用程序名
@RequestMapping中值要和需要调用的控制器方法URL相同
方法返回值要和调用控制器方法返回值相同
方法名称随意,没有要求
FeignClient注解参数:
- name:指定FeignClient的名称,如果项目使用了Ribbon,name属性会作为微服务的名称,用于服务发现
- url: url一般用于调试,可以手动指定@FeignClient调用的地址
- decode404:当发生http 404错误时,如果该字段位true,会调用decoder进行解码,否则抛出FeignException
- configuration: Feign配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contract
- fallback: 定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口
- fallbackFactory: 工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码
- path: 定义当前FeignClient的统一前缀
3.4 新建控制器
controller:
serviceImpl:
注入ServiceDemoFeign,进行远程服务调用:
在实现类中直接注入OpenFeign接口对象即可。没有在启动类上添加@EnableFeignClients时可能会报编译错误。
/**
*
* @author qzz
*/
@Service
public class OpenFeignDemoServiceImpl implements OpenFeignDemoService {
/**
* 注入service-demo服务接口(在要使用的地方注入ServiceDemoFeign)
*/
@Autowired
private ServiceDemoFeign serviceDemoFeign;
@Override
public String demo() {
return serviceDemoFeign.suiyi();
}
}
出现如下红线,原因是Feign在应用层默认是不开启的,
所以需要在启动类配置开启Feigin。
3.5 新建启动类
package com.example.openfeigndemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @EnableFeignClients:开启OpenFeign注解支持
*/
@SpringBootApplication
@EnableFeignClients
class OpenfeignDemoApplication {
public static void main(String[] args) {
SpringApplication.run(OpenfeignDemoApplication.class, args);
}
}
4.测试效果
在浏览器中输入http://localhost:8085/demo1,如果打印“demo”说明OpenFeign调用成功
五、 使用OpenFeign访问带有参数的控制器
1 带有简单数据类型参数
1.1 Application Service项目中添加控制器方法(被调用方)
/**
* 有参请求---简单数据类型参数
* @return
*/
@RequestMapping("/demo2")
public String demo2(@RequestParam("name")String name,@RequestParam("age")Integer age){
return "name:"+name+",age:"+age;
}
1.2 Application Client项目中Feign接口添加方法(OpenFeign接口)
@RequestParam注解必须有
如果Feign接口方法参数名和调用控制器参数名相同可以省略@RequestParam的参数
@FeignClient(name = "service-demo")
public interface ServiceDemoFeign {
/**
* 如果Feign接口方法参数名和调用控制器参数名相同可以省略@RequestParam的参数
* @return
*/
@RequestMapping("/demo2")
String demo2(@RequestParam("name")String name123, @RequestParam Integer age);
}
1.3 Application Client 项目 service、controller代码(调用方)
controller:
@RequestMapping("/demo2")
public String demo2(){
return openFeignDemoService.demo2("张三",30);
}
serviceImpl:
测试结果:
2 传递请求体数据
使用@RequestBody
注解会把对象以aplication/json
的形式传参,被调用的服务也需要加上@RequestBody
该注解,把收到的json转成对应的对象
2.1 Application Service项目中添加控制器方法(被调用方)
/**
* 请求体传参
* @param user
* @return
*/
@PostMapping("/demo3")
String demo3(@RequestBody User user){
return "接收的请求体参数为:"+ JSONObject.toJSONString(user);
}
2.2 Application Client 项目中Feign接口添加方法(OpenFeign接口)
@FeignClient(name = "service-demo")
public interface ServiceDemoFeign {
/**
* 请求体传参
* @param user
* @return
*/
@PostMapping("/demo3")
String demo3(@RequestBody User user);
}
2.3 Application Client 项目 service、controller代码(调用方)
controller:
@PostMapping("/demo3")
public String demo3(@RequestBody User user){
return openFeignDemoService.demo3(user);
}
serviceImpl:
测试结果:
3 传递数组类型
3.1 Application Service项目中添加控制器方法(被调用方)
/**
* 有参请求---数组类型
* @return
*/
@RequestMapping("/demo4")
public String demo4(@RequestParam("ids") String [] ids){
return "ids数组:"+JSONObject.toJSONString(ids);
}
3.2 Application Client 项目中Feign接口添加方法(OpenFeign接口)
@FeignClient(name = "service-demo")
public interface ServiceDemoFeign {
/**
* 数组类型传参
* @param ids
* @return
*/
@RequestMapping("/demo4")
String demo4(@RequestParam("ids") String[] ids);
}
3.3 Application Client 项目 service、controller代码(调用方)
controller:
@RequestMapping("/demo4")
public String demo4(@RequestParam("ids") String [] ids){
return openFeignDemoService.demo4(ids);
}
serviceImpl:
@Service
public class OpenFeignDemoServiceImpl implements OpenFeignDemoService {
/**
* 注入service-demo服务接口(在要使用的地方注入ServiceDemoFeign)
*/
@Autowired
private ServiceDemoFeign serviceDemoFeign;
/**
* 数组类型传参
* @param ids
* @return
*/
@Override
public String demo4(String[] ids) {
return serviceDemoFeign.demo4(ids);
}
}
测试结果:
4 既包含请求体数据,又包含普通表单数据
请求体数据必须有@RequestBody
4.1 Application Service项目中添加控制器方法(被调用方)
/**
* 有参请求---请求体数据、普通表单数据
* @return
*/
@RequestMapping("/demo5")
public String demo5(@RequestBody User user,String address){
return "id:"+user.getId()+",name:"+user.getName()+",age:"+user.getAge()+",address:"+address;
}
4.2 Application Client 项目中Feign接口添加方法(OpenFeign接口)
请求体数据必须有@RequestBody,普通表单必须有@RequestParam
@FeignClient(name = "service-demo")
public interface ServiceDemoFeign {
/**
* 有参请求---请求体数据、普通表单数据
* @return
*/
@PostMapping("/demo5")
String demo5(@RequestBody User user, @RequestParam("address") String address);
}
4.3 Application Client 项目 service、controller代码(调用方)
controller:
/**
* 有参请求---请求体数据、普通表单数据
* @return
*/
@PostMapping("/demo5")
public String demo5(@RequestBody User user,@RequestParam("address") String address){
return openFeignDemoService.demo5(user, address);
}
serviceImpl:
@Service
public class OpenFeignDemoServiceImpl implements OpenFeignDemoService {
/**
* 注入service-demo服务接口(在要使用的地方注入ServiceDemoFeign)
*/
@Autowired
private ServiceDemoFeign serviceDemoFeign;
/**
* 有参请求---请求体数据、普通表单数据
* @return
*/
@Override
public String demo5(User user, String address) {
return serviceDemoFeign.demo5(user,address);
}
}
测试结果:
六、源码下载
可点击此处进行下载