diff --git a/pigx-common/pigx-common-feign/pom.xml b/pigx-common/pigx-common-feign/pom.xml index 0990d6333..57b820032 100755 --- a/pigx-common/pigx-common-feign/pom.xml +++ b/pigx-common/pigx-common-feign/pom.xml @@ -18,6 +18,11 @@ org.springframework.cloud spring-cloud-starter-openfeign + + + org.springframework.boot + spring-boot-http-converter + org.springframework.boot spring-boot-jackson2 diff --git a/pigx-common/pigx-common-feign/src/main/java/com/pig4cloud/pigx/common/feign/PigxFeignClientConfiguration.java b/pigx-common/pigx-common-feign/src/main/java/com/pig4cloud/pigx/common/feign/PigxFeignClientConfiguration.java new file mode 100644 index 000000000..6194d5cfd --- /dev/null +++ b/pigx-common/pigx-common-feign/src/main/java/com/pig4cloud/pigx/common/feign/PigxFeignClientConfiguration.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + */ + +package com.pig4cloud.pigx.common.feign; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.http.converter.autoconfigure.ClientHttpMessageConvertersCustomizer; +import org.springframework.cloud.openfeign.support.FeignHttpMessageConverters; +import org.springframework.cloud.openfeign.support.HttpMessageConverterCustomizer; +import org.springframework.context.annotation.Bean; + +/** + * PIGX Feign 客户端子上下文配置。 + *

+ * 不加 @Configuration:该类仅通过 defaultConfiguration 注册到 Feign 子上下文, + * 避免被 pigx-boot 等主上下文组件扫描误引入。 + * + * @author lengleng + */ +public class PigxFeignClientConfiguration { + + /** + * 构建并预热 Feign 消息转换器。 + * @param customizers Spring Boot 客户端转换器定制器 + * @param cloudCustomizers Spring Cloud 转换器定制器 + * @return 已完成初始化的 FeignHttpMessageConverters + */ + @Bean + public FeignHttpMessageConverters pigxFeignHttpMessageConverters( + ObjectProvider customizers, + ObjectProvider cloudCustomizers) { + FeignHttpMessageConverters converters = new FeignHttpMessageConverters(customizers, cloudCustomizers); + // OpenFeign 5.0.2 在首次调用 getConverters() 时会先发布空列表再填充内容。 + // Feign 子上下文创建 Bean 时预热,避免并发首请求读到尚未初始化完成的空列表。 + converters.getConverters(); + return converters; + } + +} diff --git a/pigx-common/pigx-common-feign/src/main/java/com/pig4cloud/pigx/common/feign/annotation/EnablePigxFeignClients.java b/pigx-common/pigx-common-feign/src/main/java/com/pig4cloud/pigx/common/feign/annotation/EnablePigxFeignClients.java index df5c368a4..8a4321680 100644 --- a/pigx-common/pigx-common-feign/src/main/java/com/pig4cloud/pigx/common/feign/annotation/EnablePigxFeignClients.java +++ b/pigx-common/pigx-common-feign/src/main/java/com/pig4cloud/pigx/common/feign/annotation/EnablePigxFeignClients.java @@ -17,6 +17,7 @@ package com.pig4cloud.pigx.common.feign.annotation; +import com.pig4cloud.pigx.common.feign.PigxFeignClientConfiguration; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.FeignClientsConfiguration; import org.springframework.core.annotation.AliasFor; @@ -68,11 +69,15 @@ public @interface EnablePigxFeignClients { * A custom @Configuration for all feign clients. Can contain override * @Bean definition for the pieces that make up the client, for instance * {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}. + *

+ * 注意:显式指定该属性会整体替换默认值,即丢失 + * {@link PigxFeignClientConfiguration} 的消息转换器预热能力;如需自定义, + * 请把 {@code PigxFeignClientConfiguration.class} 一并加入数组。 * * @see FeignClientsConfiguration for the defaults */ @AliasFor(annotation = EnableFeignClients.class) - Class[] defaultConfiguration() default {}; + Class[] defaultConfiguration() default { PigxFeignClientConfiguration.class }; /** * List of classes annotated with @FeignClient. If not empty, disables classpath diff --git a/pigx-common/pigx-common-feign/src/test/java/com/pig4cloud/pigx/common/feign/PigxFeignClientConfigurationTests.java b/pigx-common/pigx-common-feign/src/test/java/com/pig4cloud/pigx/common/feign/PigxFeignClientConfigurationTests.java new file mode 100644 index 000000000..e86a7741b --- /dev/null +++ b/pigx-common/pigx-common-feign/src/test/java/com/pig4cloud/pigx/common/feign/PigxFeignClientConfigurationTests.java @@ -0,0 +1,48 @@ +package com.pig4cloud.pigx.common.feign; + +import com.pig4cloud.pigx.common.feign.annotation.EnablePigxFeignClients; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.support.StaticListableBeanFactory; +import org.springframework.boot.http.converter.autoconfigure.ClientHttpMessageConvertersCustomizer; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.cloud.openfeign.support.FeignHttpMessageConverters; +import org.springframework.cloud.openfeign.support.HttpMessageConverterCustomizer; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.annotation.AnnotationAttributes; + +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.assertj.core.api.Assertions.assertThat; + +class PigxFeignClientConfigurationTests { + + @EnablePigxFeignClients + private static class TestApplication { + } + + @Test + void shouldInitializeConvertersBeforePublishingBean() { + AtomicBoolean initialized = new AtomicBoolean(); + StaticListableBeanFactory clientCustomizerFactory = new StaticListableBeanFactory(); + clientCustomizerFactory.addBean("initializationProbe", + (ClientHttpMessageConvertersCustomizer) builder -> initialized.set(true)); + StaticListableBeanFactory cloudCustomizerFactory = new StaticListableBeanFactory(); + + FeignHttpMessageConverters converters = new PigxFeignClientConfiguration().pigxFeignHttpMessageConverters( + clientCustomizerFactory.getBeanProvider(ClientHttpMessageConvertersCustomizer.class), + cloudCustomizerFactory.getBeanProvider(HttpMessageConverterCustomizer.class)); + + assertThat(initialized).isTrue(); + assertThat(converters.getConverters()).isNotEmpty(); + } + + @Test + void shouldRegisterWarmupConfigurationForAllFeignClients() { + AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(TestApplication.class, + EnableFeignClients.class); + + assertThat(attributes).isNotNull(); + assertThat(attributes.getClassArray("defaultConfiguration")).contains(PigxFeignClientConfiguration.class); + } + +}