微服务架构 --- OpenFeign的项目实战操作

目录

一.什么是OpenFeign?

二.OpenFeign的特性:

三.如何实现远程服务调用:

1.RestTemplate实现:

(1) 将RestTemplate注册为一个Bean:

(2)使用 RestTemplate发送 http 请求:

2.OpenFeign的实现:

(1)导入依赖:

(2)启用OpenFeign功能:

(3)使用FeignClient:

使用OpenFeign的好处:

四.OpenFeign在项目中的完整使用:

1.连接池:

(1)导入依赖:

(2)开启连接池:

2.项目规范实践:

(1)创建hm-api模块并引入依赖:

 (2)在hm-api模块的com.hmall.api目录下创建Client,Entity这两个包并创建想要创建的Client和实体类:

(3)扫描Client包:

3.配置日志:

(1)在hm-api下的com.hmall.api.config目录下创建DefaultFeignConfig类:

(2)配置:

 总结:


一.什么是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 还支持日志、错误处理、超时控制等特性,为开发者提供了强大的定制能力。

猜你喜欢

转载自blog.csdn.net/2302_79840586/article/details/142885903