前言
总感觉远程调用微服务使用Ribbon + RestTemplate这样的组合有点麻烦,因为要先注册RestTemplate的Bean到Spring中,然后使用它提供的getForEntity或其他方法来发出HTTP请求获取,感觉这样有点不太好用,那么有没有其他更简便好用的方法来实现微服务的远程调用呢?Netflix公司提供了更好的封装,那就是Feign。
1、Feign介绍
Feign是Netflix公司开源的轻量级rest客户端,使用Feign可以非常方便的实现Http 客户端。Feign是一种负载均衡的HTTP客户端, 使用Feign调用API就像调用本地方法一样,从避免了调用目标微服务时,需要不断的解析/封装json 数据的繁琐。Feign集成了Ribbon。Ribbon+eureka是面向微服务编程,而Feign是面向接口编程。
Fegin是一个声明似的web服务客户端,它使得编写web服务客户端变得更加容易。使用Fegin创建一个接口并对它进行注解。它具有可插拔的注解支持包括Feign注解与JAX-RS注解,Feign还支持可插拔的编码器与解码器,Spring Cloud 增加了对 Spring MVC的注解,Spring Web 默认使用了HttpMessageConverters, Spring Cloud 集成 Ribbon 和 Eureka 提供的负载均衡的HTTP客户端 Feign。
2、Feign的优点
Feign旨在使编写Java Http客户端变得更容易。在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。
Feign集成了Ribbon。利用Ribbon维护了MicroServiceCloud-Dept的服务列表信息,并且通过轮循实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。
总结:Feign是对Ribbon的进一步封装,目的是让远程调用更加简便。
3、Feign的使用
新建一个maven工程,test-feign。
(1) pom依赖
我是直接复制别的工程,很多依赖用不上:
<dependencies>
<!-- spring-data-jpa依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
<!-- orm依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.2.11.RELEASE</version>
</dependency>
<!-- mongodb依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
<!-- 依赖的model模块 -->
<dependency>
<groupId>com.ycz</groupId>
<artifactId>ycz-model</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 依赖api模块 -->
<dependency>
<groupId>com.ycz</groupId>
<artifactId>ycz-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 依赖的web模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.6.RELEASE</version>
</dependency>
<!-- druid数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<!-- mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<!-- mybatis与spring整合包 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<!-- http客户端 -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.0</version>
</dependency>
<!-- eureka客户端依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.2.2.RELEASE</version>
<exclusions>
<!-- 排除掉jackson-core包,否则会冲突 -->
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
<!-- 排除到gson包 -->
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- feign依赖包 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>11.0</version>
</dependency>
<!-- 测试包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.3.1.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
下面这两个依赖是我新添加的,与feign有关:
<!-- feign依赖包 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>11.0</version>
</dependency>
(2)yml配置
这里只贴关键部分:
## eureka配置
eureka:
## 客户端
client:
## 服务注册开关,需要打开
register-with-eureka: true
## 服务发现开关,需要打开
fetch-registry: true
## 向两个注册中心中注册
service-url:
defaultZone: http://localhost:20000/eureka/
## 实例地址
instance:
## 将自己的IP地址注册到服务注册中心
prefer-ip-address: true
ip-address: 127.0.0.1
## 指定注册后实例的ID,这里用工程名:端口号
instance-id: ${spring.application.name}:${server.port}
# Ribbon配置
ribbon:
# 最大连接次数,在Eureka中可以找到服务,但是连接不上时会重试连接
MaxAutoRetries: 5
#切换实例的重试次数
MaxAutoRetriesNextServe: 3
#对所有操作请求都进行重试,如果是get则可以,如果是post,put等操作
#没有实现幂等的情况下是很危险的,所以设置为false
OkToRetryOnAllOperations: false
# 请求连接的超时时间
ConnectTimeout: 500000
# 请求处理的超时时间
ReadTimeout: 600000
(3)启动类
@SpringBootApplication
//包扫描
@EntityScan(basePackages = {"com.ycz.domain.person"})
@ComponentScan(basePackages = {"com.ycz.api"})
@ComponentScan(basePackages = {"com.ycz.test.feign"})
@EnableDiscoveryClient
@EnableFeignClients
public class FeignTestApplication {
public static void main(String[] args) {
SpringApplication.run(FeignTestApplication.class, args);
}
}
这里加了一个@EnableFeignClients注解,表明这个是Feign的客户端,需要远程调用别的微服务。简单来说,A服务想要通过Feign来调用B服务,那么就在A服务的启动类上添加@EnableFeignClients注解。
(4)微服务列表
全部的微服务名称可以在一个接口中定义好,那么调用哪个微服务时直接引入这个接口中规定的哪一个就行了,该接口主要是定义微服务名称的常量:
public interface ServiceInstaceList {
public static final String TEST_SWAGGER = "test-swagger";
}
我这里只定义了一个微服务。
(5)Feign代理接口
@FeignClient(name = ServiceInstaceList.TEST_SWAGGER)
public interface PersonClient {
@GetMapping("/person/list/{address}")
QueryResponseResult<Person> findPersonsByAddress(@PathVariable("address") String address);
}
要想调用别的微服务,需要定义一个Feign的代理,也就是说Feign来代理请求别的微服务的接口,@FeignClient注解是标明你要调用哪个微服务,指定名称。下面的方法要跟你要调用微服务接口一模一样,其中通过url传参基本类型必须加@PathVariable注解,传对象要加@RequestBody转换。
(6)Service
@Service
public class TestService {
@Autowired
PersonClient personClient;
public QueryResponseResult<Person> getPersonList(String address){
return personClient.findPersonsByAddress(address);
}
}
代理接口定义好了,在Service层里直接引入就行了。
(7)controller
@RestController
@RequestMapping("/test")
public class TestFeignController implements FeignControllerApi{
@Autowired
TestService testService;
@Override
@GetMapping("/feign/{address}")
public QueryResponseResult<Person> testFeign(@PathVariable("address") String address) {
return testService.getPersonList(address);
}
}
然后这里的FeignControllerApi如下:
@Api(value = "测试远程调用",description = "调用远程接口",tags = {"测试远程调用"})
public interface FeignControllerApi {
QueryResponseResult<Person> testFeign(String address);
}
接口其实是最先定义好的,在controller中暴露出来,注意,返回类型如果是复杂类型,必须要有无参构造函数,否则会报错。
(8)测试
启动eureka服务中心,要调用的微服务,以及test-feign服务,启动完毕先看看eureka中心注册的服务:
两个微服务都注册进来了,OK,下面使用swagger测试。
直接进入swagger测试:
输入参数测试:
返回结果:
返回结果正确,那么测试就OK了。
4、总结
使用Feign来调用远程微服务接口更加的方便,A调用B,在A的启动类上加上Feign客户端的注解,然后定义一个代理接口,通过这个代理接口就可以调用远程B服务的接口,HTTP请求由Feign来帮我们发送,非常好用。