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

19.springcloud_openfeign之案例

文章目录

  • 一、前言
  • 二、案例
    • 通用配置
    • @EnableFeignClients注解上的配置
      • 1、包扫描
      • 2、指定clients
      • 3、使用EnableFeignClients#defaultConfiguration指定全局配置
    • @FeignClient注解配置
    • 属性文件配置
    • 注解使用
      • 类注解
        • @CollectionFormat
      • 方法注解
        • @CollectionFormat
        • @RequestMapping
      • 参数注解
    • 上传文件
    • 熔断器
    • 缓存
    • 拦截器
  • 三、总结

一、前言

前面已经介绍完了feign的基本功能以及springcloud_openfeign的扩展功能, 本节我们将介绍springcloud_openfeign的使用案例

二、案例

通用配置

pom gav

<dependencies><dependency><groupId>io.github.openfeign</groupId><artifactId>feign-okhttp</artifactId><version>13.3</version></dependency><dependency><groupId>io.github.openfeign</groupId><artifactId>feign-jackson</artifactId><version>13.3</version></dependency><!-- 提供处理java8的日期和时间类的扩展模块,如(LocalDate, LocalDateTime, ZonedDateTime 等) --><dependency><groupId>com.fasterxml.jackson.datatype</groupId><artifactId>jackson-datatype-jsr310</artifactId><version>2.17.1</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId><version>4.1.3</version></dependency><!-- https://github.com/OpenFeign/feign-form --><dependency><groupId>io.github.openfeign.form</groupId><artifactId>feign-form</artifactId><version>3.8.0</version></dependency></dependencies>

配置文件

application.yml

spring:cloud:openfeign:cache:enabled: false  # 关闭请求缓存

@EnableFeignClients注解上的配置

1、包扫描

启动类

@SpringBootApplication
@EnableFeignClients(basePackages = "per.qiao.feign.starter.remote.interfacepkg", basePackageClasses={Interface1.class, Interface2.class})
public class FeignStudyApplication {public static void main(String[] args) {SpringApplication.run(FeignStudyApplication.class, args);}
}

2、feign接口定义

package per.qiao.feign.starter.remote.clientspkg;import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;@FeignClient(contextId = "interface1", name = "interface1", url = "http://localhost:8080", path = "/packageInterface")
public interface Interface1 {@GetMapping(value = "/scanTest")String scanTest();
}package per.qiao.feign.starter.remote.clientspkg;import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;@FeignClient(contextId = "interface2", name = "interface2", url = "http://localhost:8080", path = "/packageInterface")
public interface Interface2 {@GetMapping(value = "/scanTest")String scanTest();
}package per.qiao.feign.starter.remote.interfacepkg;import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;@FeignClient(contextId = "package-interface", name = "package-interface", url = "http://localhost:8080", path = "/packageInterface")
public interface PackageInterface {@GetMapping(value = "/scanTest")String scanTest();}

其中Interface1Interface2是通过basePackageClasses指定的, PackageInterface是通过basePackages包扫描的

controller

@RestController
@RequestMapping("packageInterface")
public class Controller2 {@GetMapping("scanTest")public String scanTest() {System.out.println("=== scanTest");return "scanTest";}
}

2、指定clients

修改启动类上注解即可, 此时包扫描将会失效

@SpringBootApplication
@EnableFeignClients(clients = {Interface1.class, Interface2.class})
public class FeignStudyApplication {public static void main(String[] args) {SpringApplication.run(FeignStudyApplication.class, args);}
}

3、使用EnableFeignClients#defaultConfiguration指定全局配置

启动类

@SpringBootApplication
@EnableFeignClients(basePackages = "per.qiao.feign.starter.remote.clientspkg", defaultConfiguration = {GlobalInterceptor.class})
public class FeignStudyApplication {public static void main(String[] args) {SpringApplication.run(FeignStudyApplication.class, args);}
}

全局拦截器

public class GlobalInterceptor implements RequestInterceptor {@Overridepublic void apply(RequestTemplate template) {System.out.println("全局拦截器生效...");}
}

测试类

@SpringBootTest
public class FeignDemo {@Autowiredprivate Interface1 interface1;@Autowiredprivate Interface2 interface2;@Testpublic void scanTest() {System.out.println(interface1.scanTest());System.out.println(interface2.scanTest());}
}

它会将配置的组件注册到每个子容器中, 多例对象, 而不是共享对象

defaultConfiguration可以指定的类型有

序号类名说明
1FeignLoggerFactory.class定义如何创建 Feign 的日志记录器工厂,控制日志的输出逻辑。
2Feign.Builder.classFeign 客户端的构建器,用于创建 Feign 客户端实例。
3Encoder.class负责将请求对象编码为 HTTP 请求的实现类。
4Decoder.class负责将 HTTP 响应解码为目标对象的实现类。
5Contract.class定义 Feign 如何解析接口上的注解并生成 HTTP 请求的契约。
6FeignClientConfigurer.class是否启用属性中的配置
7Logger.Level.class定义 Feign 的日志级别(NONE、BASIC、HEADERS、FULL)。
8Retryer.class定义 Feign 客户端请求失败后的重试逻辑。
9ErrorDecoder.class用于处理 Feign 请求过程中发生的异常,并生成自定义的错误信息。
10FeignErrorDecoderFactory.class用于创建 ErrorDecoder 实例的工厂类。
11Request.Options.class配置请求超时参数(连接超时和读取超时)。
12RequestInterceptor.class请求拦截器,用于在请求发送前进行自定义操作(如添加 Header)。
13ResponseInterceptor.class响应拦截器,用于在响应处理后进行额外操作。
14QueryMapEncoder.class@QueryMap,@SpringQueryMap,@HeaderMap的编码器
15ExceptionPropagationPolicy.class重试异常的传播机制, 是抛出重试异常还是原始异常。
16Capability.classFeign 的扩展能力类,用于增强Feign.Builder中各组件的功能 。
17Client.class请求客户端,例如okhttpClient
18Targeter.classfeign.Target的包装类

@FeignClient注解配置

序号属性名称说明
1value指定 Feign 客户端的名称,用于在上下文中唯一标识该客户端(与 name 等效)。
2contextIdspringcloud_openfeign子容器的名称, 这个要唯一, 如果为空, 它将取name名称, 非必须
3name定义 Feign 客户端的名称,与 value 等效,优先推荐使用 name。唯一且必须; 可以是占位符
4qualifiers别名
5url明确指定服务的基础 URL,跳过服务发现(如 nacos等)。非必须; 可以是占位符
6dismiss404是否忽略 HTTP 404 响应,如果设置为 true,则不抛出异常。
7configuration引入自定义配置类,用于定制 Feign 客户端的行为(如编码器、拦截器等)。子容器独享; 与上面defaultConfiguration可选值相同
8fallback指定降级处理类的实现,当服务不可用时提供备用逻辑。与fallbackFactory两者取其一。优先级高于fallbackFactory; 需要开启熔断配置
9fallbackFactory指定降级工厂类,用于生成降级处理的实例,可以包含更多逻辑。与fallback两者取其一; 需要开启熔断配置
10path在请求路径前添加公共前缀。一般对应contoller的路径; 非必须
11primary设置该 Bean 是否为注入的首选。如果按照类型注入可以有多个的时候, 会注入带有@primary属性的

需要注意: 如果url为空, 那么url将取值name属性, 并且name的值会从环境上下文中获取

yml配置

spring:cloud:openfeign:cache:enabled: false  # 关闭请求缓存circuitbreaker:enabled: true # 开启熔断器

feign接口

@FeignClient(contextId = "package-interface", name = "package-interface", url = "http://localhost:8080", path = "/packageInterface", qualifiers = {"remote1"}, dismiss404 = false, configuration={PackageInterfaceInterceptor.class}, fallback = PackageInterfaceFallback.class)
public interface PackageInterface {@GetMapping(value = "/scanTest")String scanTest();
}

拦截器

public class PackageInterfaceInterceptor implements RequestInterceptor {@Overridepublic void apply(RequestTemplate template) {System.out.println("客户端私有拦截器");}
}

熔断配置, 它需要注入到spring容器

/*** * 熔断器工厂*/
@Configuration
public class MyCircuitBreakerFactory extends CircuitBreakerFactory {@Overridepublic CircuitBreaker create(String id) {System.out.println("熔断器id=" + id);return new MyCircuitBreaker();}@Overrideprotected ConfigBuilder configBuilder(String id) {return null;}@Overridepublic void configureDefault(Function defaultConfiguration) {}
}/*** 熔断器*/
public class MyCircuitBreaker implements CircuitBreaker {private AtomicLong failureCount = new AtomicLong();@Overridepublic <T> T run(Supplier<T> toRun, Function<Throwable, T> fallback) {try {return toRun.get();}catch (Throwable throwable) {failureCount.incrementAndGet();return fallback.apply(throwable);}}
}/*** 熔断回调*/
@Component
public class PackageInterfaceFallback implements PackageInterface {@Overridepublic String scanTest() {return "异常了";}
}

controller

@RestController
@RequestMapping("packageInterface")
public class Controller2 {@GetMapping("scanTest")public String scanTest() {System.out.println("=== scanTest");return "scanTest";}
}

请求

@SpringBootTest
public class FeignDemo {/** 这里用别名 */@Autowired@Qualifier("remote1")private PackageInterface packageInterface;@Testpublic void scanTest() {System.out.println(packageInterface.scanTest());}
}

正常情况下响应

熔断器id=PackageInterfacescanTest
客户端私有拦截器
全局拦截器生效...
scanTest

异常情况响应(这里直接将服务端关闭就行)

熔断器=PackageInterfacescanTest
客户端私有拦截器
全局拦截器生效...
熔断异常了

属性文件配置

属性配置文件是否启动的开关配置如下

@Bean
public FeignClientConfigurer feignClientConfigurer() {return new FeignClientConfigurer() {@Overridepublic boolean inheritParentConfiguration() {// 为true则启动, false则禁用return true;}};
}

属性配置如下

spring:cloud:openfeign:cache:enabled: falsecircuitbreaker:enabled: false # 开启熔断器client: # feign的组件配置defaultToProperties: true # 优先使用配置文件中的配置defaultConfig: default  # 指定全局配置是下面config中的那一个,默认是default, 它将给所有子容器添加多例对象config:default:  # 默认配置, 将对所有的feign客户端生效loggerLevel: NONE	# 默认关闭日志打印retryer: feign.Retryer.Default  # 设置重试机制interface1:  # interface1的配置型logger-level: basic  # 指定日志级别dismiss404: trueinterface2:logger-level: headers	# 指定日志级别dismiss404: true

logback.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration><appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern></encoder></appender><!-- 开启debug日志级别 --><root level="debug"><appender-ref ref="CONSOLE" /></root>
</configuration>

注意这里需要开启debug日志级别

执行

@SpringBootTest
public class FeignDemo {@Autowiredprivate Interface1 interface1;@Autowiredprivate Interface2 interface2;@Testpublic void propertiesTest() {System.out.println(interface1.scanTest());System.out.println(interface2.scanTest());}
}

结果

interface1使用的basic日志级别; interface2使用的是headers日志级别

-- interface1.scanTest()的日志打印结果
[Interface1#scanTest] ---> GET http://localhost:8080/packageInterface/scanTest HTTP/1.1
[Interface1#scanTest] <--- HTTP/1.1 200 (6ms)-- interface2.scanTest()的日志打印
[Interface2#scanTest] ---> GET http://localhost:8080/packageInterface/scanTest HTTP/1.1
[Interface2#scanTest] ---> END HTTP (0-byte body)
[Interface2#scanTest] <--- HTTP/1.1 200 (0ms)
[Interface2#scanTest] connection: keep-alive
[Interface2#scanTest] content-length: 8
[Interface2#scanTest] content-type: text/plain;charset=UTF-8
[Interface2#scanTest] date: Sat, 28 Dec 2024 20:44:08 GMT
[Interface2#scanTest] keep-alive: timeout=60
[Interface2#scanTest] <--- END HTTP (8-byte body)

注解使用

类注解

@CollectionFormat

用于指定集合参数添加到url请求上时的分隔符号

// controller
@GetMapping("classCollectionFormatTest")
public String classCollectionFormatTest(@RequestParam String names) {System.out.println("=== classCollectionFormatTest: " + names);return "classCollectionFormatTest";
}// feign接口  这里指定分隔符为|
@FeignClient(contextId = "interface1", name = "interface1", url = "http://localhost:8080", path = "/packageInterface")
@CollectionFormat(feign.CollectionFormat.PIPES)
public interface Interface1 {@GetMapping(value = "/classCollectionFormatTest")String classCollectionFormatTest(@RequestParam List<String> names);
}// 测试
@Test
public void collectionFormatTest() {System.out.println(interface1.classCollectionFormatTest(List.of("uncleqiao", "小杜同学")));
}// 结果
=== classCollectionFormatTest: uncleqiao|小杜同学

方法注解

@CollectionFormat
// controller
@GetMapping("methodCollectionFormatTest")
public String methodCollectionFormatTest(@RequestParam String names) {System.out.println("=== methodCollectionFormatTest: " + names);return "methodCollectionFormatTest";
}// feign接口  这里指定分隔符为|
@FeignClient(contextId = "interface1", name = "interface1", url = "http://localhost:8080", path = "/packageInterface")
@CollectionFormat(feign.CollectionFormat.PIPES)
public interface Interface1 {// 这里指定分隔符为,@GetMapping(value = "/classCollectionFormatTest")@CollectionFormat(feign.CollectionFormat.CSV)String methodCollectionFormatTest(@RequestParam List<String> names);
}// 测试
@Test
public void collectionFormatTest() {System.out.println(interface1.methodCollectionFormatTest(List.of("uncleqiao", "小杜同学")));
}// 结果
=== classCollectionFormatTest: uncleqiao,小杜同学
@RequestMapping
// controller
@PostMapping("requestMappingTest")
public Person requestMappingTest(@RequestBody Person person, @RequestHeader HttpHeaders headers) {System.out.println("=== requestMappingTest: " + person);System.out.println("=== requestMappingTest 收到请求头myhearders: " + headers.getFirst("myhearders"));person.setName("小杜同学");return person;
}// feign接口
@RequestMapping(value = "/requestMappingTest", method = RequestMethod.POST, produces = "application/json", consumes = "application/json", headers = "myhearders=aaa")
Person requestMappingTest(Person person);// 测试
@Test
public void requestMappingTest() {System.out.println(interface1.requestMappingTest(new Person("uncleqiao", 18, 1, LocalDate.now())));
}// 服务度结果
=== requestMappingTest: Person(name=uncleqiao, age=18, gender=1, birthday=2024-12-29)
=== requestMappingTest 收到请求头myhearders: aaa// 客户端结果
Person(name=小杜同学, age=18, gender=1, birthday=2024-12-29)

关于@GetMapping@PostMapping, 它们实际就是用的@RequestMapping, 只是指定了对应的method, 然后用@AliasFor指向到RequestMapping上, 使用起来和@RequestMapping一样

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {/*** Alias for {@link RequestMapping#name}.*/@AliasFor(annotation = RequestMapping.class)String name() default "";
}@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.POST)
public @interface PostMapping {/*** Alias for {@link RequestMapping#name}.*/@AliasFor(annotation = RequestMapping.class)String name() default "";// ....
}

参数注解

  1. @PathVariable: url上的path变量
  2. @MatrixVariable: 矩阵参数; 需要注意的是feign会将对应的参数进行u8编码,;=将会编码成%3B%3D,需要额外处理一下
  3. @RequestHeader: 请求头
  4. @RequestBody: 请求体
  5. @SpringQueryMap: url上的path参数
  6. @RequestPart: body参数
  7. @CookieValue: 请求头上的cookie, 它会覆盖@RequestHeader中的cookie参数
@RestController
@RequestMapping("paramAnnonController")
public class ParamAnnonController {@GetMapping("/matrixVariableTest/{name}")public String matrixVariableTest(@PathVariable(name = "name") String name,@MatrixVariable(name = "age") Integer age) {System.out.println("uncleqiao 收到name:" + name);System.out.println("uncleqiao 收到age:" + age);return "matrixVariableTest";}@PostMapping("/pathVariableTest/{name}/{age}")public String pathVariableTest(@PathVariable(name = "name", required = false) String name,@PathVariable(name = "age") Integer age,@RequestBody Person person) {System.out.println("uncleqiao 收到name:" + name);System.out.println("uncleqiao 收到age:" + age);System.out.println("uncleqiao 收到person:" + person);return "pathVariableTest";}@PostMapping("/requestHeaderTest")public String requestHeaderTest(@RequestHeader HttpHeaders headers) {System.out.println("uncleqiao 收到请求头们:" + headers);return "requestHeaderTest";}@GetMapping("/springQueryMapTest")public String springQueryMapTest(@RequestParam String name, @RequestParam Integer age) {System.out.println("springQueryMapTest 收到:" + name);System.out.println("springQueryMapTest 收到:" + age);return "springQueryMapTest";}@GetMapping("/springQueryMapTest2")public String springQueryMapTest2(Person person) {System.out.println("springQueryMapTest2 收到:" + person);return "springQueryMapTest2";}@PostMapping("/requestPartTest")public String requestPartTest(@RequestBody Person person) {System.out.println("requestPartTest 收到:" + person);return "requestPartTest";}@PostMapping("/requestPartTest2")public String requestPartTest2(@RequestPart String name, @RequestPart Integer age) {System.out.println("requestPartTest2 收到:" + name);System.out.println("requestPartTest2 收到:" + age);return "requestPartTest2";}@GetMapping("/cookieTest")public String cookieTest(@CookieValue String name, @CookieValue String session) {System.out.println("cookieTest 收到:" + name);System.out.println("cookieTest 收到:" + session);return "cookieTest";}}

feign接口

@FeignClient(contextId = "paramAnnoRemote", name = "paramAnnoRemote", path = "/paramAnnonController", url = "localhost:8080")
public interface ParamAnnoRemote {@GetMapping(value = "/matrixVariableTest/{name}{age}")String matrixVariableTest(@PathVariable String name, @MatrixVariable Integer age);@PostMapping(value = "/pathVariableTest/{name}/{age}")String pathVariableTest(@PathVariable("name") String name, @PathVariable("age") Integer age, @PathVariable("gender") Integer gender);@PostMapping(value = "/requestHeaderTest")String requestHeaderTest(@RequestHeader("Content-Type") String contentType, @RequestHeader("myHeader") Map<String, String> myHeader);@GetMapping(value = "/springQueryMapTest")String springQueryMapTest(@SpringQueryMap Map<String, Object> map);@GetMapping(value = "/springQueryMapTest2")String springQueryMapTest2(@SpringQueryMap Map<String, Object> map);@PostMapping(value = "/requestPartTest")String requestPartTest(@RequestPart("name") String name, @RequestPart("age") Integer age);@PostMapping(value = "/requestPartTest2")String requestPartTest2(@RequestPart String name, @RequestPart Integer age);@GetMapping(value = "/cookieTest")String cookieTest(@CookieValue("name") String name, @CookieValue("session") String session);
}

测试类

@SpringBootTest
public class ParamAnnoRemoteTest {@Autowiredprivate ParamAnnoRemote paramAnnoRemote;@Testvoid matrixVariableTest() {String person = paramAnnoRemote.matrixVariableTest("小杜同学", 18);System.out.println(person);}@Testvoid pathVariableTest() {String person = paramAnnoRemote.pathVariableTest( "小杜同学", 18, 1);System.out.println(person);}@Testvoid requestHeaderTest() {Map<String, String> myHeaderMap = Map.of("myHeader1", "abc", "myHeader2", "def");String person = paramAnnoRemote.requestHeaderTest("application/json", myHeaderMap);System.out.println(person);}@Testvoid springQueryMapTest() {Map<String, Object> param = Map.of("name", "小乔同学", "age", 18);String person = paramAnnoRemote.springQueryMapTest(param);System.out.println(person);}@Testvoid springQueryMapTest2() {Map<String, Object> param = Map.of("name", "小乔同学", "age", 18);String person = paramAnnoRemote.springQueryMapTest2(param);System.out.println(person);}@Testvoid requestPartTest() {String person2 = paramAnnoRemote.requestPartTest("小乔同学", 20);System.out.println(person2);}@Testvoid cookieTest() {String person2 = paramAnnoRemote.cookieTest("zs", "abc");System.out.println(person2);}
}

对于@MatrixVariable注解需要处理分号和逗号

@Configuration
public class InterceptorConfig {@Beanpublic RequestInterceptor matrixVariableInterceptor() {return requestTemplate -> {// 避免;和=被编码String url = requestTemplate.url();url = url.replaceAll("%3B", ";");url = url.replaceAll("%3D", "=");requestTemplate.uri(url);System.out.println("requestTemplate.url()===" + url);};}
}

上传文件

// controller
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String uploadFile(@RequestParam("file") MultipartFile file) {if (file.isEmpty()) {return "文件为空";}// 示例逻辑:打印文件信息String fileInfo = "File uploaded: " + file.getOriginalFilename() + ", size: " + file.getSize();System.out.println(fileInfo);return fileInfo;
}@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
String uploadFile(@RequestPart("file") MultipartFile file);// 测试类
@Test
void uploadFile() throws Exception {File file = new File("your file path");FileInputStream input = new FileInputStream(file);MultipartFile multipartFile = new MockMultipartFile("file", file.getName(), "text/plain", input);String result = paramAnnoRemote.uploadFile(multipartFile);System.out.println(result);
}

说明: 这里实际依赖的是feign-form-spring底层就是就是io.github.openfeign.form:feign-form-spring:3.8.0

这里测试类也可以是客户端controller接口接收的MultipartFile文件作为feign接口的调用参数

熔断器

1、开启熔断器

spring:cloud:openfeign:cache:enabled: falsecircuitbreaker:enabled: true # 开启熔断器

2、配置熔断器

需要指定CircuitBreakerFactory对象 以及 创建熔断器对象

@Configuration
public class MyCircuitBreakerFactory extends CircuitBreakerFactory {@Overridepublic CircuitBreaker create(String id) {return new MyCircuitBreaker();}@Overrideprotected ConfigBuilder configBuilder(String id) {return null;}@Overridepublic void configureDefault(Function defaultConfiguration) {}
}/*** 熔断器*/
public class MyCircuitBreaker implements CircuitBreaker {private AtomicLong failureCount = new AtomicLong();@Overridepublic <T> T run(Supplier<T> toRun, Function<Throwable, T> fallback) {try {return toRun.get();}catch (Throwable throwable) {failureCount.incrementAndGet();return fallback.apply(throwable);}}
}

3、接口与调用

@FeignClient(contextId = "circuitBreakerRemote", name = "circuitBreakerRemote", url = "localhost:8080",  path = "/circuitBreakerController", fallback = CircuitBreakerRemoteFallback.class)
public interface CircuitBreakerRemote {@GetMapping(value = "/circuitBreakerTest")String circuitBreakerTest();
}// 熔断回调
@Component
public class CircuitBreakerRemoteFallback implements CircuitBreakerRemote {@Overridepublic String circuitBreakerTest() {return "circuitBreakerTest fallback";}
}// 测试
@SpringBootTest
public class CircuitBreakerTest {@Autowiredprivate CircuitBreakerRemote circuitBreakerRemote;@Testpublic void circuitBreakerTest() {System.out.println(circuitBreakerRemote.circuitBreakerTest());}
}// 结果
circuitBreakerTest fallback

缓存

1、开启缓存

spring:cloud:openfeign:cache:enabled: true

2、定义缓存拦截器

@Configuration
public class MyCacheInterceptorConfig {@Bean@ConditionalOnMissingBean(CacheOperationSource.class)public CacheOperationSource cacheOperationSource() {return new AnnotationCacheOperationSource(false);}@Beanpublic org.springframework.cache.interceptor.CacheInterceptor myCacheInterceptor(CacheOperationSource cacheOperationSource) {CacheInterceptor cacheInterceptor = new CacheInterceptor();cacheInterceptor.setCacheOperationSource(cacheOperationSource);return cacheInterceptor;}}

3、接口与调用

// controller
@RestController
@RequestMapping("/cacheableController")
public class CacheableController {@RequestMapping("/cacheableTest")public Person cacheableTest(@RequestBody Person person) {System.out.println("cacheableTest:" + person);person.setName("小杜同学");return person;}
}// feign接口
@FeignClient(contextId = "cacheableRemote", name = "cacheableRemote", path = "/cacheableController", url = "localhost:8080")
public interface CacheableRemote {@PostMapping(value = "/cacheableTest", consumes = "application/json", produces = "application/json")@Cacheable(cacheNames = "demoCache", key = "'person'+#p0.name")Person cacheableTest(Person person);}// 调用
@Test
public void cacheableTest() {Person person = this.cacheableRemote.cacheableTest(new Person("uncleqiao", 18, 1, LocalDate.now()));Person person2 = this.cacheableRemote.cacheableTest(new Person("uncleqiao", 18, 1, LocalDate.now()));System.out.println(person);System.out.println(person2);
}// 服务端打印
cacheableTest:Person(name=uncleqiao, age=18, gender=1, birthday=2024-12-29)// 客户端打印
Person(name=小杜同学, age=18, gender=1, birthday=2024-12-29)
Person(name=小杜同学, age=18, gender=1, birthday=2024-12-29)

多次调用, 只要指定的缓存key一样, 就不会真正发起调用请求

拦截器

1、请求拦截器定义

public class InterfaceConfigInterceptor implements RequestInterceptor {@Overridepublic void apply(RequestTemplate template) {System.out.println("feign接口指定私有拦截器生效...");}
}public class PropertiesContextIdInterceptor implements RequestInterceptor {@Overridepublic void apply(RequestTemplate template) {System.out.println("properties指定子容器私有拦截器生效...");}
}@Configuration
public class InterceptorConfig {@Beanpublic RequestInterceptor beanGlobalInterceptor() {return requestTemplate -> {System.out.println("全局注入拦截器beanGlobalInterceptor===");};}
}public class PropertiesGlobalInterceptor implements RequestInterceptor {@Overridepublic void apply(RequestTemplate template) {System.out.println("properties配置文件全局拦截器生效...");}
}public class EnableInterceptor implements RequestInterceptor {@Overridepublic void apply(RequestTemplate template) {System.out.println("EnableFeignClients指定全局拦截器生效...");}
}

2、feign接口

使用configuration属性指定拦截器

@FeignClient(contextId = "interceptorRemote", name = "interceptorRemote", path = "/interceptorController", url = "localhost:8080", configuration = {InterfaceConfigInterceptor.class})
public interface InterceptorRemote {@GetMapping(value = "/interceptorTest")String interceptorTest(@RequestParam String name);}

3、配置文件

spring:cloud:openfeign:client: # feign的组件配置defaultToProperties: true # 优先使用配置文件中的配置defaultConfig: default  # 指定全局配置是下面config中的那一个,默认是default, 它将给所有子容器添加多例对象config:default:  # 默认配置, 将对所有的feign客户端生效loggerLevel: NONErequest-interceptors:- per.qiao.feign.starter.interceptors.PropertiesGlobalInterceptorinterceptorRemote:request-interceptors:- per.qiao.feign.starter.interceptors.PropertiesContextIdInterceptor

这里使用配置文件配置了 全局拦截器和子容器(单个feign客户端/feign接口)对应的拦截器

4、启动类指定全局拦截器

@EnableFeignClients(clients = {InterceptorRemote.class}, defaultConfiguration = {EnableInterceptor.class})
public class FeignStudyApplication {@Autowired(required = false)private UrlDemoRemote urlDemoRemote;private ApplicationContext parent;public static void main(String[] args) {SpringApplication.run(FeignStudyApplication.class, args);}
}

执行结果

feign接口指定私有拦截器生效...
EnableFeignClients指定全局拦截器生效...
全局注入拦截器beanGlobalInterceptor===
properties配置文件全局拦截器生效...
properties指定子容器私有拦截器生效...

注意上面spring.cloud.openfeign.client.defaultToProperties=false会让全局配置失效, 因为子容器中获取的组件项采取的是覆盖的做法; 详见FeignClientFactoryBean#configureFeign->#configureUsingConfiguration

三、总结

  1. feign框架本身较为简单, 但是扩展点是非常多的, 使用者可以几乎对每一个功能点做定制化;
  2. 利用springcloud的父子容器特性将每个feign接口(客户端)隔离成单独的子容器,有点类似类加载器的隔离; 这里不同于spring的作用域的隔离机制
  3. 写通用并且扩展性强的代码是非常重要的, 同时也需要一定的技术功底

至此整个feign的介绍就完毕了。


http://www.mrgr.cn/news/81910.html

相关文章:

  • 如何在 Ubuntu 22.04 上优化 Apache 以应对高流量网站教程
  • 马原复习笔记
  • 串口 + DMA
  • 【网络安全实验室】SQL注入实战详情
  • MySQL日志体系的深度解析:功能与差异
  • Frontend - 分页(针对 python / Django )
  • Snowflake基础知识
  • WPF 绘制过顶点的圆滑曲线 (样条,贝塞尔)
  • Qt之QtConcurrent
  • 【服务器项目部署】⭐️将本地项目部署到服务器!
  • 数仓建模:如何进行实体建模?
  • 大模型在自动驾驶领域的应用和存在的问题
  • MySQL数据库的备份与恢复你会了吗?
  • ubuntu2204 gpu 没接显示器,如何连接vnc
  • 3.2、SDH帧结构
  • Rust : tokio中select!
  • 【机器学习】【朴素贝叶斯分类器】从理论到实践:朴素贝叶斯分类器在垃圾短信过滤中的应用
  • Elasticsearch名词解释
  • C++ 设计模式:中介者模式(Mediator Pattern)
  • gesp(二级)(16)洛谷:B4037:[GESP202409 二级] 小杨的 N 字矩阵
  • 自定义 Element Plus 树状表格图标
  • ArcGIS Pro地形图四至角图经纬度标注与格网标注
  • html+css+js网页设计 美食 家美食1个页面
  • 【Rust自学】8.3. String类型 Pt.1:字符串的创建、更新与拼接
  • 被裁20240927 --- 嵌入式硬件开发 STM32篇
  • SonarQube相关的maven配置及使用