目录
(2)在hm-api模块的com.hmall.api目录下创建Client,Entity这两个包并创建想要创建的Client和实体类:
(1)在hm-api下的com.hmall.api.config目录下创建DefaultFeignConfig类:
一.什么是OpenFeign?
OpenFeign 是一个 声明式 HTTP 客户端,它允许我们以简单的接口方式调用其他微服务的 RESTful API。在微服务架构中,各个服务之间需要进行通信,而 OpenFeign 提供了优雅且简洁的方式,通过注解将远程 HTTP 调用映射为 Java 接口方法调用,开发者无需手动拼接 URL 或处理复杂的 HTTP 请求。
二.OpenFeign的特性:
- 声明式调用:通过接口和注解定义 HTTP 请求,不需要直接操作 HTTP 客户端代码。
- 负载均衡支持:支持与 Ribbon 结合实现客户端负载均衡。
- 与 Eureka 结合:可以自动解析微服务名,调用其他微服务的 REST API。
- 集成 Spring Boot:无缝集成 Spring Cloud,支持自动配置和注入。
- 支持重试与超时:可以配置请求的重试机制和超时时间。
- 支持日志记录:可以记录详细的请求与响应日志,便于调试。
三.如何实现远程服务调用:
前提:我想在cart-service模块远程调用访问item-service模块的方法接口
1.RestTemplate实现:
在最开始,我们可以使用RestTemplate来实现服务的远程调用,但是写出的代码非常的复杂:
-
注册RestTemplate到Spring容器
-
调用RestTemplate的API发送请求,常见方法有:
-
getForObject:发送Get请求并返回指定类型对象
-
PostForObject:发送Post请求并返回指定类型对象
-
put:发送PUT请求
-
delete:发送Delete请求
-
exchange:发送任意类型请求,返回ResponseEntity
-
(1) 将RestTemplate注册为一个Bean:
@MapperScan("com.hmall.cart.mapper")
@SpringBootApplication
public class CartApplication {
public static void main(String[] args) {
SpringApplication.run(CartApplication.class, args);
}
//RestTemplate注册
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
Spring Boot 启动类本身就是一个配置类,在启动类中可以基于条件(如环境变量、配置文件)动态注册不同的 Bean,以此减少了分散配置文件的复杂性,使代码更加直观。
(2)使用 RestTemplate发送 http 请求:
-
① 请求方式
-
② 请求路径
-
③ 请求参数
-
④ 返回值类型
@Service
@RequiredArgsConstructor //推荐restTemplate以构造方法导入,这个注解仅构造final修饰的成员属性
public class CartServiceImpl extends ServiceImpl<CartMapper, Cart> implements ICartService {
private final RestTemplate restTemplate;
//用discoveryClient客户端完成服务的拉取
private final DiscoveryClient discoveryClient;
private void handleCartItems(List<CartVO> vos) {
// TODO 1.获取商品id
Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
// 2.查询商品
List<ItemDTO> items = itemClient.queryItemByIds(itemIds);
// 2.1根据服务名称获取服务的实例列表
List<ServiceInstance> instances = discoveryClient.getInstances("item-service");
if(CollUtils.isEmpty(instances)){
return ;
}
// 2.2手写负载均衡,从实例列表中挑选实例
ServiceInstance serviceInstance = instances.get(RandomUtil.randomInt(instances.size()));
// 2.3利用RestTemplate发起http请求,得到http的响应
ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
serviceInstance.getUri() + "/items?{ids}",
HttpMethod.GET,
null,
//写集合泛型的class,因为new的对象可以拿到泛型,随后用反射就可以拿到对象的数据类型返回值
new ParameterizedTypeReference<List<ItemDTO>>() {},
Map.of("ids",CollUtils.join(itemIds,","))//集合拼接
);
// 2.4解析响应
if(!response.getStatusCode().is2xxSuccessful()){
//查询失败,直接结束11
return ;
}
List<ItemDTO> items = response.getBody();
if (CollUtils.isEmpty(items)) {
return;
}
// 剩余代码...
}
}
我们可以发现,这段代码非常的复杂且庞大,而且这种调用方式,与原本的本地方法调用差异太大,编程时的体验也不统一,一会儿远程调用,一会儿本地调用。因此,我们必须想办法改变远程调用的开发模式,让远程调用像本地方法调用一样简单。而这就要用到 OpenFeign 组件了。
2.OpenFeign的实现:
根据上面的RestTemplate的实现,我们可以发现,远程调用的核心就是上面的四部,所以OpenFeign就利用SpringMVC的相关注解来声明上述4个参数,然后基于动态代理帮我们生成远程调用的代码,而无需我们手动再编写,非常方便。
工作原理:
OpenFeign 的核心思想是通过 动态代理,在程序运行时创建代理对象。调用接口时,Feign 会根据配置的注解,自动生成 HTTP 请求,并发送给目标微服务或外部 API。
(1)导入依赖:
<!--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>
(2)启用OpenFeign功能:
我们在client包下创建ItemClient,以此来满足需求在cart-service模块远程调用访问item-service模块的方法接口。
package com.hmall.cart.client;
import com.hmall.cart.domain.dto.ItemDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@FeignClient("item-service")
public interface ItemClient {
@GetMapping("/items")
List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
}
这里只需要声明接口,无需实现方法。接口中的几个关键信息:
-
@FeignClient("item-service")
:声明服务名称 -
@GetMapping
:声明请求方式 -
@GetMapping("/items")
:声明请求路径 -
@RequestParam("ids") Collection<Long> ids
:声明请求参数 -
List<ItemDTO>
:返回值类型
有了上述信息,OpenFeign就可以利用动态代理帮我们实现这个方法,并且向http://item-service/items
发送一个GET
请求,携带ids为请求参数,并自动将返回值处理为List<ItemDTO>
。
我们只需要直接调用这个方法,即可实现远程调用了。
(3)使用FeignClient:
最后,我们在 cart-service
的 com.hmall.cart.service.impl.CartServiceImpl
中改造代码,直接调用 ItemClient
的方法:
@Service
@RequiredArgsConstructor //推荐restTemplate以构造方法导入,这个注解仅构造final修饰的成员属性
public class CartServiceImpl extends ServiceImpl<CartMapper, Cart> implements ICartService {
private final ItemClient itemClient;//因为ItemClient是个接口,没有实现,需要动态代理创建Bean,而动态代理的使用前提是需要将包被Spring扫描到,所以要在启动类加上扫描Client包地址
private void handleCartItems(List<CartVO> vos) {
// TODO 1.获取商品id
Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
// 2.查询商品
List<ItemDTO> items = itemClient.queryItemByIds(itemIds);
if (CollUtils.isEmpty(items)) {
return;
}
// 剩余代码...
}
}
使用OpenFeign的好处:
OpenFeign替我们完成了服务拉取、负载均衡、发送http请求的所有工作,是不是看起来优雅多了。而且,这里我们不再需要RestTemplate了,还省去了RestTemplate的注册。
四.OpenFeign在项目中的完整使用:
1.连接池:
Feign底层发起http请求,依赖于其它的框架。其底层支持的http客户端实现包括:
-
HttpURLConnection:默认实现,不支持连接池
-
Apache HttpClient :支持连接池
-
OKHttp:支持连接池
因此我们通常会使用带有连接池的客户端来代替默认的HttpURLConnection。比如,我们使用OK Http.
(1)导入依赖:
<!--OK http 的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
(2)开启连接池:
在application.yml文件中开启Feign连接池的功能:
feign:
okhttp:
enabled: true # 开启OKHttp功能
随后重启服务,连接池即可生效。
2.项目规范实践:
我们可以再当前的项目结构抽取一个模块hm-api,在这个模块存放我们想要使用的Client以及实体类。如果我们在哪个模块功能想要使用对应的Client,我们只需要引入hm-api的依赖,然后再启动类加入注解开启OpenFeign即可。
(1)创建hm-api模块并引入依赖:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.heima</groupId>
<artifactId>hmall</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>hm-api</artifactId>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!--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>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.6.6</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
(2)在hm-api模块的com.hmall.api目录下创建Client,Entity这两个包并创建想要创建的Client和实体类:
package com.hmall.api.client;
import com.hmall.api.dto.ItemDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Collection;
import java.util.List;
@FeignClient("item-service")
public interface ItemClient {
@GetMapping("/item")
List<ItemDTO> queryItemByIds(@RequestParam("Ids") Collection<Long> ids);
}
(3)扫描Client包:
在cart-service模块引入hm-api模块:
<!--feign模块-->
<dependency>
<groupId>com.heima</groupId>
<artifactId>hm-api</artifactId>
<version>1.0.0</version>
</dependency>
这个时候要注意,ItemClient没有创建对应的Bean,这是为什么呢?
因为ItemClient是个接口,没有实现,需要动态代理创建Bean,而动态代理的使用前提是需要将包被Spring扫描到,所以要在cart-service模块的启动类加上扫描Client包地址。
@MapperScan("com.hmall.cart.mapper")
@SpringBootApplication
@EnableFeignClients(basePackages = "com.hmall.api.client",defaultConfiguration = DefaultFeignConfig.class) //启动OpenFeign
public class CartApplication {
public static void main(String[] args) {
SpringApplication.run(CartApplication.class, args);
}
}
这个 defaultConfiguration = DefaultFeignConfig.class 是启动OpenFeign的debug日志的,后面解释。
也可以使用下面的形式扫描要用的Client的字节码:
@MapperScan("com.hmall.cart.mapper")
@SpringBootApplication
//@EnableFeignClients(basePackages = "com.hmall.api.client") //启动OpenFeign
@EnableFeignClients(clients = {ItemClient.class},defaultConfiguration = DefaultFeignConfig.class) //启动OpenFeign
public class CartApplication {
public static void main(String[] args) {
SpringApplication.run(CartApplication.class, args);
}
}
3.配置日志:
OpenFeign只会在FeignClient所在包的日志级别为DEBUG时,才会输出日志。而且其日志级别有4级:
-
NONE:不记录任何日志信息,这是默认值。
-
BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
-
HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
-
FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
Feign默认的日志级别就是NONE,所以默认我们看不到请求日志。
(1)在hm-api下的com.hmall.api.config目录下创建DefaultFeignConfig类:
import feign.Logger;
import org.springframework.context.annotation.Bean;
//定义日志级别(FULL级)
public class DefaultFeignConfig {
@Bean
public Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
(2)配置:
接下来,要让日志级别生效,还需要配置这个类。有两种方式:
-
局部生效:在某个
FeignClient
中配置,只对当前FeignClient
生效
@FeignClient(value = "item-service", configuration = DefaultFeignConfig.class)
-
全局生效:在
@EnableFeignClients
中配置,针对所有FeignClient
生效。
@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)
总结:
OpenFeign 作为 Spring Cloud 生态的一部分,为微服务之间的通信提供了简洁、声明式的解决方案。通过 OpenFeign,开发者可以轻松调用其他微服务的 API,减少手动编写 HTTP 请求的繁琐代码。也可以结合 RabbitMQ 和 Nacos,它可以实现负载均衡和服务发现。此外,OpenFeign 还支持日志、错误处理、超时控制等特性,为开发者提供了强大的定制能力。