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