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*/// @Beanpublic 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 {@Resourceprivate 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 {@Overridepublic 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-server1main: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: truemime-types: text/xml,application/xml,application/jsonmin-request-size: 2048response:enabled: trueautoconfiguration:jackson: # 你可以考虑启用Jackson模块来支持org.springframework.data.domain.Page和org.springframework.data.domain.Sort解码enabled: truelazy-attributes-resolution: true #@FeignClient 延迟解析okhttp: #要使用OKHttpClient支持的伪客户端,请确保OKHttpClient在你的类路径中,并将spring.cloud.openfeign.okhttp.enabled设置为true。enabled: truereadTimeout: 5000hc5:enabled: false #确保HttpClient 5在类路径上httpclient:enabled: falsecircuitbreaker: #断路由enabled: trueclient:default-to-properties: falseconfig:default: #默认配置connectTimeout: 1000readTimeout: 1000loggerLevel: BASICstoreClient: #客户端配置,@FeignClient name名称 和 @FeignClient contextId,在负载均衡的场景中,它还对应于将用于检索实例的服务器应用程序的serviceIdurl: http://localhost:8082 #服务的url,该版本不支持此属性,@FeignClient(name = "storeClient", url = "${feign.client.config.storeClient.url}")connectTimeout: 5000readTimeout: 5000loggerLevel: 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: headerValuerequestInterceptors: #自定义拦截器- org.example.app1.interceptor.StoreInterceptor
# capabilities:
# - com.example.FooCapability
# - com.example.BarCapability
# queryMapEncoder: com.example.SimpleQueryMapEncoder
# metrics.enabled: falseserver: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