OpenFeign服务接口调用
在使用Spring Cloud开发微服务应用时,可使用RestTemplate+Ribbon的方式实现服务接口的远程调用。
package com.hl.service.impl;import com.hl.clients.OrderClient;
import com.hl.pojo.Order;
import com.hl.service.OrderService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;import java.util.Map;@Service
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {private final RestTemplate restTemplate;@Overridepublic Order getOrder() {Map map = restTemplate.exchange("http://localhost:8082/order",HttpMethod.GET,null,Map.class).getBody();Order order = new Order();if (map != null) {order = new Order(1, (String) map.get("name"), (String) map.get("address"));}return order;}
}
但这种方式需要填写远程地址,并配置相关参数,那么有没有更简单的方式呢?还可以使用OpenFeign来实现服务接口的远程调用。OpenFeign是一个声明式的HTTP客户端,可以在程序中像调用本地方法一样调用服务接口,并且支持负载均衡。
一、基于OpenFeign的微服务接口调用
OpenFeign 是一个声明式的 Web Service 客户端,它使得编写 HTTP 客户端变得更简单。通过使用 Feign,开发者只需创建一个接口并在上面添加注解即可完成服务调用。
主要特点
-
声明式 API:通过接口和注解定义服务调用
-
集成 Ribbon:支持客户端负载均衡
-
集成 Hystrix:支持服务熔断(需额外配置)
-
支持 Spring MVC 注解:如
@RequestMapping
,@PathVariable
等
远程调用的关键点就在于四个:
-
请求方式
-
请求路径
-
请求参数
-
返回值类型
所以,OpenFeign就利用SpringMVC的相关注解来声明上述4个参数,然后基于动态代理帮我们生成远程调用的代码,而无需我们手动再编写,非常方便。
1. OpenFeign基本使用
将服务提供者和服务消费者整合到Nacos注册中心之后,给服务消费者添加Feign依赖,并创建Feign接口,服务消费者使用Feign接口来调用服务提供者。下面以一个简单的案例说明如何使用OpenFeign实现服务调用。
案例说明:分别创建一个简单的微服务模拟服务提供者和服务消费者,并将其整合到Nacos注册中心,然后通过OpenFeign实现服务调用。
1.1 父工程
创建父工程统一管理Spring Boot、Spring Cloud和Spring Cloud Alibaba。父工程统一管理子模块依赖的版本号,pom.xml文件代码如下所示。
<?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"><modelVersion>4.0.0</modelVersion><groupId>com.hl</groupId><artifactId>shop</artifactId><version>1.0.0</version><packaging>pom</packaging><modules><module>order-consumer</module><module>order-provider</module></modules><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.12</version><relativePath/></parent><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><spring-cloud.version>2021.0.3</spring-cloud.version><spring-cloud-alibaba.version>2021.0.4.0</spring-cloud-alibaba.version><spring-boot-web.version>2.7.12</spring-boot-web.version></properties><dependencyManagement><dependencies><!--spring cloud--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency><!--spring cloud alibaba--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${spring-cloud-alibaba.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.22</version></dependency><!--单元测试--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency></dependencies></project>
1.2 service-provider微服务——服务提供者
① 在父工程中创建service-provider微服务来模拟服务提供者。修改service-provider微服务中的pom.xml文件,代码如下所示。
<?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"><modelVersion>4.0.0</modelVersion><parent><groupId>com.hl</groupId><artifactId>shop</artifactId><version>1.0.0</version></parent><artifactId>service-provider</artifactId><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--nacos 服务注册发现--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency></dependencies></project>
② 在resources目录下创建application.yaml文件,配置服务端口号为9001、微服务名为
“service-provider”、Nacos注册中心地址为“localhost:8848”,代码如下所示。
server:port: 9001
spring:application: name: service-provider cloud:nacos:discovery:server-addr: localhost:8848
③ 创建项目启动类ServiceProviderApplication,在该启动类上追加@EnableDiscoveryClient
注解,开启服务注册与发现功能,代码如下所示。
package com.hl;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;@SpringBootApplication
@EnableDiscoveryClient
public class ServiceProviderApplication{public static void main(String[] args) {SpringApplication.run(ServiceProviderApplication.class, args);}
}
④ 创建OrderController类,在该类上追加@RestController注解,在该类中定义一个getOrder()方法。
package com.hl.controller;import com.hl.properties.OrderProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;
import java.util.Map;@RestController
@RequiredArgsConstructor
public class OrderController {private final OrderProperties orderProperties;@GetMapping("/order")public Map<String, String> getOrder() {System.out.println(orderProperties.getName());System.out.println(orderProperties.getAddress());Map<String, String> map = new HashMap<>();map.put("name", orderProperties.getName());map.put("address", orderProperties.getAddress());return map;}
}
1.3 service-consumer微服务——服务消费者
在父工程中创建service-consumer微服务来模拟服务消费者。
① 修改pom.xml文件,在<dependencies></dependencies>标签中添加OpenFeign依赖spring-cloud-starter-openfeign,代码如下所示。
<?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"><modelVersion>4.0.0</modelVersion><parent><groupId>com.hl</groupId><artifactId>shop</artifactId><version>1.0.0</version></parent><artifactId>service-consumer</artifactId><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--nacos 服务注册发现--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!--openFeign--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!--负载均衡器--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency></dependencies></project>
② 在resources目录下创建application.yaml文件,配置服务端口号为8081、微服务名为
“service-consumer”、Nacos注册中心地址为“localhost:8848”,代码如下所示。
server:port: 8081
spring:application:name: service-consumerprofiles:active: devcloud:nacos:server-addr: localhost:8848 # nacos地址
③ 创建项目启动类ServiceConsumerApplication,在该启动类上追加@EnableDiscoveryClient注解和@EnableFeignClients注解,代码如下所示。
package com.hl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class ServiceConsumerApplication{public static void main(String[] args) {SpringApplication.run(ServiceConsumerApplication.class, args);}
}
④ 创建Feign接口OrderClient ,使用@FeignClient注解声明要调用的服务 “service-provider”,使用@GetMapping注解声明要调用的接口“/order”,代码如下所示。
package com.hl.clients;import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;import java.util.Map;@FeignClient("service-provider")
public interface OrderClient {@GetMapping("/order")public Map<String, String> getOrder();
}
⑤ 创建OrderServiceImpl 类,在该类上追加@Service注解,在该类中注入OrderClient 接口,然后调用该接口,代码如下所示。
其中feign替我们完成了服务拉取、负载均衡、发送http请求的所有工作
package com.hl.service.impl;import com.hl.clients.OrderClient;
import com.hl.pojo.Order;
import com.hl.service.OrderService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;import java.util.Map;@Service
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {private final OrderClient orderClient;@Overridepublic Order getOrder() {Map<String, String> map = orderClient.getOrder();Order order = new Order();if (map != null) {order = new Order(1, (String) map.get("name"), (String) map.get("address"));}return order;}
}
⑥编写测试类OrderTest
package com.hl;import com.hl.pojo.Order;
import com.hl.service.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
public class OrderTest {@Autowiredprivate OrderService orderService;@Testpublic void test() {Order order = orderService.getOrder();System.out.println(order);}
}
在IDEA工具中启动两个微服务:service-provider和service-consumer。启动Nacos,访问http://localhost:8848/nacos,查看“服务管理”下的“服务列表”,可发现service-provider和service-consumer微服务实例
运行测试类OrderTest的test方法,验证远程调用是否生效
2. 连接池
Feign底层发起http请求,依赖于其它的框架。其底层支持的http客户端实现包括:
-
HttpURLConnection:默认实现,不支持连接池
-
Apache HttpClient :支持连接池
-
OKHttp:支持连接池
因此我们通常会使用带有连接池的客户端来代替默认的HttpURLConnection。比如,我们使用OK Http。
2.1.引入依赖
在service-consumer的pom.xml
中引入依赖:
<!--OK http 的依赖 -->
<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-okhttp</artifactId>
</dependency>
2.2.开启连接池
在service-consumer的application.yml
配置文件中开启Feign的连接池功能:
feign:okhttp:enabled: true # 开启OKHttp功能
重启服务,连接池就生效了。
3. OpenFeign日志管理
OpenFeign只会在FeignClient所在包的日志级别为DEBUG时,才会输出日志。而且其日志级别有4级:
-
NONE:不记录任何日志信息,这是默认值。
-
BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
-
HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
-
FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
Feign默认的日志级别就是NONE,所以默认我们看不到请求日志。
3.1 包日志级别
定位到service-consumer微服务的application.yaml文件,新增配置项logging.level,声明接口的包名,代码如下所示。
server:port: 8081
spring:application:name: service-consumerprofiles:active: devcloud:nacos:server-addr: localhost:8848 # nacos地址
logging:level:com.hl: debug
3.2 日志配置类
package com.hl.config;import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class DefaultFeignConfig{@Beanpublic Logger.Level level() {//FULL日志级别return Logger.Level.FULL;}
}
3.3 配置
接下来,要让日志级别生效,还需要配置这个类。有两种方式:
-
局部生效:在某个
FeignClient
中配置,只对当前FeignClient
生效
@FeignClient(value = "item-service", configuration = DefaultFeignConfig.class)
-
全局生效:在
@EnableFeignClients
中配置,针对所有FeignClient
生效。
@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)
重新运行测试方法,返回IDEA控制台,可以看到Feign接口名、调用的方法名等内容
4. OpenFeign超时控制
OpenFeign默认等待返回接口的数据时间是1s,超过1s就会报错。这种情况下大部分接口没有问题,但是不能排除部分接口耗时较长,在执行过程中用时超过1s。若直接返回报错信息并不是最合理的选择。OpenFeign提供了超时设置。
feign:client:config:default:# 超时时间,单位毫秒,默认2000connect-timeout: 5000# 读取超时时间,单位毫秒,默认5000read-timeout: 5000