当前位置: 首页 > article >正文

spring boot2.7集成OpenFeign 3.1.7

1.Feign

Feign是一个声明式web服务客户端。它使编写web服务客户端更容易。要使用Feign,请创建一个接口并对其进行注释。它具有可插入注释支持,包括Feign注释和JAX-RS注释。Feign还支持可插拔编码器和解码器。Spring Cloud增加了对Spring MVC注释的支持,并支持使用与Spring Web中默认使用的HttpMessageConverters相同的HttpMessageConverters。Spring Cloud集成了Eureka、Spring Cloud CircuitBreaker以及Spring Cloud LoadBalancer,在使用Feign时提供负载均衡的http客户端。

文档地址:https://docs.spring.io/spring-cloud-openfeign/docs/3.1.7/reference/html/#spring-cloud-feign-circuitbreaker

2.创建maven项目spring-cloud-feign-demo

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>org.example</groupId>
    <artifactId>spring-cloud-feign-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <modules>
        <module>feign-app1</module>
        <module>feign-app2</module>
    </modules>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <!-- 统一依赖管理 -->
        <spring.boot.version>2.7.8</spring.boot.version>
        <!-- openfeign-->
        <openfeign.version>3.1.7</openfeign.version>
<!--        <openfeign.version>4.0.6</openfeign.version>-->
        <!-- loadbalancer-->
        <loadbalancer.version>3.1.7</loadbalancer.version>
        <!-- hutool工具类-->
        <hutool.version>5.8.11</hutool.version>
        <!-- feign-okhttp-->
        <feign-okhttp.version>11.10</feign-okhttp.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- 统一依赖管理 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring.boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- openfeign-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
                <version>${openfeign.version}</version>
            </dependency>
            <!-- loadbalancer-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-loadbalancer</artifactId>
                <version>${loadbalancer.version}</version>
            </dependency>
            <!-- hutool工具类-->
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>${hutool.version}</version>
            </dependency>

            <!-- https://mvnrepository.com/artifact/io.github.openfeign/feign-okhttp -->
            <dependency>
                <groupId>io.github.openfeign</groupId>
                <artifactId>feign-okhttp</artifactId>
                <version>${feign-okhttp.version}</version>
            </dependency>

        </dependencies>

    </dependencyManagement>

</project>

3.创建子项目客户端feign-app1

3.1 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>org.example</groupId>
        <artifactId>spring-cloud-feign-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>feign-app1</artifactId>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

        <!-- Web 相关 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <!-- feign-okhttp-->
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-okhttp</artifactId>
        </dependency>

        <!-- loadbalancer-->
<!--        <dependency>-->
<!--            <groupId>org.springframework.cloud</groupId>-->
<!--            <artifactId>spring-cloud-starter-loadbalancer</artifactId>-->
<!--        </dependency>-->

        <!-- hutool工具类-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>

    </dependencies>

</project>

创建org.example.app1.dto.Store

package org.example.app1.dto;

/**
 * @Version Store v1.0.0 2024/11/25 14:50 $$
 */
public class Store {

    private Long id;
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

3.2 创建 org.example.app1.client.StoreClient

package org.example.app1.client;

import org.example.app1.dto.Store;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.util.List;

/**
 * contextId 如果我们想要创建多个具有相同名称或url的虚拟客户端,以便它们指向相同的服务器,但每个客户端都有不同的自定义配置,那么我们必须使用@FeignClient的contextId属性,以避免这些配置bean的名称冲突
 */
@FeignClient(name = "storeClient", url = "${feign.client.config.storeClient.url}")
public interface StoreClient {
    @RequestMapping(method = RequestMethod.GET, value = "/stores")
    List<Store> getStores();

    @RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
    Store update(@PathVariable("storeId") Long storeId, Store store);

    @RequestMapping(method = RequestMethod.DELETE, value = "/stores/{storeId:\\d+}")
    void delete(@PathVariable("storeId") Long storeId);

}


3.3 创建配置文件:org.example.app1.config.StoreClientConfiguration

package org.example.app1.config;

import feign.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

/**
 * @Author tanyong
 * @Version StoreClientConfig v1.0.0 2024/11/25 15:35 $$
 */
@Configuration
public class StoreClientConfiguration {


    /**
     * 要在每个客户端基础上禁用Spring Cloud断路器支持,请创建一个vanilla Feign。具有“prototype”作用域的构建器
     *
     * @return
     */
    @Bean
    @Scope("prototype")
    public Feign.Builder feignBuilder() {
        return Feign.builder();
    }


    /**
     * 默认情况下创建类型为Retryer的NEVER_RETRY,它将禁用重试。
     * 请注意,这种重试行为与Feign默认的行为不同,后者会自动重试ioexception,
     * 将它们视为与网络相关的瞬态异常,以及从ErrorDecoder抛出的任何RetryableException。
     *
     * @return
     */
    // @Bean
    public Retryer feignRetryer() {
        return new Retryer.Default(100, java.util.concurrent.TimeUnit.SECONDS.toMillis(1), 3);
    }
}

3.4 创建org.example.app1.controller.StoreTestController

package org.example.app1.controller;

import org.example.app1.client.StoreClient;
import org.example.app1.dto.Store;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;


/**
 * @Author tanyong
 * @Version StoreController v1.0.0 2024/11/25 14:55 $$
 */
@RestController
public class StoreTestController {

    @Resource
    private StoreClient storeClient;

    @RequestMapping(method = RequestMethod.GET, value = "/stores")
    List<Store> getStores() {
        return storeClient.getStores();
    }

    @RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
    Store update(@PathVariable("storeId") Long storeId, Store store) {

        return storeClient.update(storeId, store);
    }

    @RequestMapping(method = RequestMethod.DELETE, value = "/stores/{storeId:\\d+}")
    void delete(@PathVariable Long storeId) {
        storeClient.delete(storeId);
    }
}

3.5 创建 org.example.app1.interceptor.StoreInterceptor

package org.example.app1.interceptor;

import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.stereotype.Component;

/**
 * @Author tanyong
 * @Version RequestInterceptor v1.0.0 2024/11/26 17:00 $$
 */
@Component
public class StoreInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        template.header("Authorization", "Bearer your-token");
    }
}

3.7 创建org.example.app1.DemoApplication1

package org.example.app1;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * @Author tanyong
 * @Version DemoApplication1 v1.0.0 2024/11/25 14:30 $$
 */
@SpringBootApplication
@EnableFeignClients
public class DemoApplication1 {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication1.class, args);
    }
}

3.8 application.yaml 配置

spring:
  application:
    name: demo-server1
  main:
    allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。
##############===load-balanced==######################
#    discovery: #服务配置
#      client:
#        simple:
#          instances:
#            stores:
#              - uri: http://localhost:8082


##############===feign配置==######################
feign:
  compression: # GZIP 压缩 spring.cloud.openfeign.okhttp.enabled设置为true时,我们不启用压缩。
    request:
      enabled: true
      mime-types: text/xml,application/xml,application/json
      min-request-size: 2048
    response:
      enabled: true

  autoconfiguration:
    jackson: # 你可以考虑启用Jackson模块来支持org.springframework.data.domain.Page和org.springframework.data.domain.Sort解码
      enabled: true

  lazy-attributes-resolution: true #@FeignClient 延迟解析
  okhttp: #要使用OKHttpClient支持的伪客户端,请确保OKHttpClient在你的类路径中,并将spring.cloud.openfeign.okhttp.enabled设置为true。
    enabled: true
    readTimeout: 5000
  hc5:
    enabled: false #确保HttpClient 5在类路径上
  httpclient:
    enabled: false

  circuitbreaker: #断路由
    enabled: true

  client:
    default-to-properties: false
    config:
      default: #默认配置
        connectTimeout: 1000
        readTimeout: 1000
        loggerLevel: BASIC
      storeClient: #客户端配置,@FeignClient name名称 和 @FeignClient contextId,在负载均衡的场景中,它还对应于将用于检索实例的服务器应用程序的serviceId
        url: http://localhost:8082 #服务的url,该版本不支持此属性,@FeignClient(name = "storeClient", url = "${feign.client.config.storeClient.url}")
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: FULL #NONE, No logging (DEFAULT).BASIC, 只记录请求方法和URL以及响应状态代码和执行时间。HEADERS, 标头,记录基本信息以及请求和响应标头。FULL, 记录请求和响应的标头、正文和元数据。
        #errorDecoder: com.example.SimpleErrorDecoder
        #retryer: com.example.SimpleRetryer 默认情况下,使用类型Retryer创建,它将禁用重试。请注意,这种重试行为与Feign默认的行为不同,后者会自动重试ioexception,将它们视为与网络相关的瞬态异常,以及从ErrorDecoder抛出的任何RetryableException
#            defaultQueryParameters: 指定查询参数,这些参数和头将随feignName客户端的每个请求一起发送。
#              query: queryValue
#            defaultRequestHeaders: 指定查询头,这些参数和头将随feignName客户端的每个请求一起发送。
#              header: headerValue
        requestInterceptors: #自定义拦截器
          - org.example.app1.interceptor.StoreInterceptor
#          capabilities:
#            - com.example.FooCapability
#            - com.example.BarCapability
#            queryMapEncoder: com.example.SimpleQueryMapEncoder
#            metrics.enabled: false

server:
  port: 8081


# 日志文件配置
logging:
  level:
    org.example.app1.client: debug

3.9 logback-spring.xml 配置

<configuration>
    <!-- 定义日志文件的存储路径 -->
    <property name="LOG_PATH" value="logs" />
    <property name="LOG_FILE" value="${LOG_PATH}/app.log" />

    <!-- 控制台输出 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} - %level - [%thread] - %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 文件输出 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_FILE}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天生成一个新的日志文件 -->
            <fileNamePattern>${LOG_PATH}/app.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 保留最近30天的日志文件 -->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} - %level - [%thread] - %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 日志级别配置 -->
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="FILE" />
    </root>
</configuration>

4. 创建子项目服务端feign-app2

复制客户端代码,创建org.example.app2.controller.StoreController

package org.example.app2.controller;

import cn.hutool.core.collection.CollUtil;
import org.example.app2.dto.Store;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;


/**
 * @Author tanyong
 * @Version StoreController v1.0.0 2024/11/25 14:55 $$
 */
@RestController
public class StoreController {

    @RequestMapping(method = RequestMethod.GET, value = "/stores")
    List<Store> getStores() {
        Store s = new Store();
        s.setId(1L);
        s.setName("app2");
        return CollUtil.newArrayList(s);
    }

    @RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
    Store update(@PathVariable("storeId") Long storeId, Store store) {
        return store;
    }

    @RequestMapping(method = RequestMethod.DELETE, value = "/stores/{storeId:\\d+}")
    void delete(@PathVariable Long storeId) {

    }
}

5.启动服务

postman调用成功响应:
在这里插入图片描述
客户端打印调用日志:
在这里插入图片描述

简单集成成功,完整配置查看:https://docs.spring.io/spring-cloud-openfeign/docs/3.1.7/reference/html/appendix.html


http://www.kler.cn/a/416817.html

相关文章:

  • UE5 打包报错 Unknown structure 的解决方法
  • Conda 管理python开发环境
  • FreeSWITCH 简单图形化界面36 -使用mod_sms发送短消息
  • C++ 字符串中数字识别
  • AI数据分析工具(二)
  • 丹摩|丹摩智算平台使用教学指南
  • 服务器配环境
  • OD E卷 - 实现【正则表达式替换】
  • 使用uni-app进行开发前准备
  • [2024年3月10日]第15届蓝桥杯青少组stema选拔赛C++中高级(第二子卷、编程题(5))
  • vue中如何获取public路径
  • Ubuntu 关机命令
  • 【LeetCode】每日一题 2024_11_30 判断是否可以赢得数字游戏(模拟)
  • NLP中的主题模型:LDA(Latent Dirichlet Allocation, 潜在狄利克雷分配)
  • vulnhub靶机之Fawkes
  • C#结合.NET框架快速构建和部署AI应用
  • 【超全总结】深度学习分割模型的损失函数类别及应用场景
  • sunshine和moonlight串流网络丢失帧高的问题(局域网)
  • SickOs: 1.1靶场学习小记
  • asyncio.run() 里面嵌套 asyncio.run() 可以吗?
  • 【Leetcode】3232.判断是否可以赢得数字游戏
  • APIPost内置函数的使用与学习
  • 利用若依代码生成器实现课程管理模块开发
  • 【QNX+Android虚拟化方案】128 - QNX 侧触摸屏驱动解析
  • 【一文读懂】大语言模型
  • Q-2A型金相试样切割机