OpenFeign 自定义解码器Decoder 失效

问题描述

项目上开发了OpenFeign的自定义解码器,用来统一处理返回结果。

开发完后测试已经生效了,过两天后,这块代码没有变动的情况下,发现请求结果突然又不走自定义的解码器了。

代码如下

解码器 BaseResponseFeignDecoder

@Slf4j
public class BaseResponseFeignDecoder implements Decoder {

    static ObjectMapper objectMapper = new ObjectMapper();

    static {
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    @Override
    public Object decode(Response response, Type type) throws IOException, FeignException {
        if (response.body() == null) {
            throw new DecodeException(response.status(), "没有返回有效的数据", response.request());
        }
        String bodyStr = Util.toString(response.body().asReader(Util.UTF_8));
        //对结果进行转换
        TypeFactory typeFactory = objectMapper.getTypeFactory();
        JavaType resultType = typeFactory.constructParametricType(BaseResponse.class, typeFactory.constructType(type));
        BaseResponse<?> result = objectMapper.readValue(bodyStr, resultType);
        //如果返回错误,且为内部错误,则直接抛出异常
        if (!BaseConstants.HTTP_RESPONSE_CODE_SUCCESS.equals(result.getCode())) {
            throw new DecodeException(response.status(), "接口返回错误:" + result.getMsg(), response.request());
        }
        return result.getData();
    }
}

配置类 BaseResponseFeignConfig

public class BaseResponseFeignConfig {

    @Bean
    public Decoder feignDecoder() {
        return new BaseResponseFeignDecoder();
    }

}

Feign接口定义 FinValidationFeign

@FeignClient(name = "masterdata", path = "/api/validation", configuration = BaseResponseFeignConfig.class)
public interface FinValidationFeign {
	// 各类feign接口
}

问题排查

由于当前代码没有变动,怀疑是解码器被别人的新开发的代码给覆盖了。但排查之后项目里并没有其他解码器相关的代码。

只能跟踪解码器的加载进行排查。

OpenFeign客户端会在应用启动时进行加载。

根据 FeignClient 注解跟踪到 org.springframework.cloud.openfeign.FeignClientsRegistrarregisterFeignClients 方法。

我们可以看到加载时,通过registerClientConfiguration 方法加载自定义配置

通过代码可以看到注册的 beanNamename + "." + FeignClientSpecification.class.getSimpleName(), 也就是 masterdata.feignClientSpecification

由此可以看出当多个Client 的 name 一致时,会使用最后一个加载的client的配置。

public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

    LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
    Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
    final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
    if (clients == null || clients.length == 0) {
        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        scanner.setResourceLoader(this.resourceLoader);
        scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
        Set<String> basePackages = getBasePackages(metadata);
        // 通过扫包将 FeignClient 注解的代码都加载出来
        for (String basePackage : basePackages) {
            candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
        }
    }
    else {
        for (Class<?> clazz : clients) {
            candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
        }
    }

    // 循环初始化Feign客户端
    for (BeanDefinition candidateComponent : candidateComponents) {
        if (candidateComponent instanceof AnnotatedBeanDefinition) {
            // verify annotated class is an interface
            AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
            AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
            Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
            // 加载 FeignClient 注解的参数
            Map<String, Object> attributes = annotationMetadata
                .getAnnotationAttributes(FeignClient.class.getCanonicalName());

            String name = getClientName(attributes);
            // 处理自定义配置, 默认值 {}, 无自定义配置也会走这步
            registerClientConfiguration(registry, name, attributes.get("configuration"));

            registerFeignClient(registry, annotationMetadata, attributes);
        }
    }
}

private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
    builder.addConstructorArgValue(name);
    builder.addConstructorArgValue(configuration);
    // 根据name + FeignClientSpecification 进行Spring的Bean注册
    registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),
                                    builder.getBeanDefinition());
}

这时候再扭头过来看这两天加的代码,发现新增了一个 同名name的client,并且没配置自定义解码器,加载顺序在 FinValidationFeign 之后,导致他的配置覆盖掉了 FinValidationFeign 。一起变成了走默认的解码器。

@FeignClient(name = "masterdata", path = "/api/query")
public interface FinQueryFeign {
	// 各类feign接口
}

解决方案

因为对应服务在重构,返回值存在两个包装类,没办法进行统一配置。

因为是beanName相同导致的配置覆盖,而我们能修改的name是通过 String name = getClientName(attributes); 获取的

可以看到 name 是优先获取 contextId , 我们可以通过配置contextId进行区分,避免覆盖。

	private String getClientName(Map<String, Object> client) {
		if (client == null) {
			return null;
		}
		String value = (String) client.get("contextId");
		if (!StringUtils.hasText(value)) {
			value = (String) client.get("value");
		}
		if (!StringUtils.hasText(value)) {
			value = (String) client.get("name");
		}
		if (!StringUtils.hasText(value)) {
			value = (String) client.get("serviceId");
		}
		if (StringUtils.hasText(value)) {
			return value;
		}

		throw new IllegalStateException(
				"Either 'name' or 'value' must be provided in @" + FeignClient.class.getSimpleName());
	}

解决后的代码

@FeignClient(name = "masterdata", contextId = "masterdata-validation", path = "/api/validation", configuration = BaseResponseFeignConfig.class)
public interface FinValidationFeign {
	// 各类feign接口
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
OpenFeign 中配置解码器可以通过自定义 FeignDecoder 来实现。以下是一个示例代码: 首先,你需要创建一个实现了 `Decoder` 接口的自定义解码器类,例如 `CustomDecoder`: ```java import feign.Response; import feign.codec.Decoder; import feign.jackson.JacksonDecoder; import org.springframework.beans.factory.ObjectFactory; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.cloud.openfeign.support.ResponseEntityDecoder; public class CustomDecoder implements Decoder { private final ResponseEntityDecoder decoder; public CustomDecoder() { this.decoder = new ResponseEntityDecoder(new JacksonDecoder()); } @Override public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException { // 在这里可以进行自定义的解码逻辑处理 return decoder.decode(response, type); } } ``` 然后,在你的 Feign 接口上使用 `@Decoder` 注解来指定使用自定义解码器,例如: ```java @FeignClient(name = "your-service", configuration = YourFeignClient.Config.class) public interface YourFeignClient { @GetMapping("/your-api") @Decoder(CustomDecoder.class) // 使用自定义解码器 YourResponseObject yourApiMethod(); class Config { // 配置其它 Feign 相关的参数 } } ``` 这样,当调用 `YourFeignClient` 的 `yourApiMethod()` 方法时,将会使用自定义解码器进行解码操作。 请注意,上述代码中的 `YourResponseObject` 是你期望的响应对象类型,你需要根据自己的实际情况进行替换。另外,还可以根据需要在 `CustomDecoder` 类中添加适合的解码逻辑。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值