@FeignClient的value值重复,多个client设置不同的configuration无效的解决方案

作者
2024-06-19阅读 1851

注:文章描述的问题还是只出现在feign 1.x版本。openfeign(2.x)版本没有此问题,无需参照一下解决办法。

接上两篇文章

feignClient + feign-from实现文件上传

feignclient的configuration设置无效,@FeignClient的value值重复解决方案

1、问题描述

上一篇文章说道,@FeignClient重名了之后,configuration会出现覆盖,从而导致了配置无效的情况。(当然)

我们只是在MultipartSupportConfig类中加入一个@Component注解就可以解决。

但是注意,这个只适合重名的所有的FeignClient中,只有一个client显式设置了configuration。

当重名的FeignClient中,有两个设置了configuration,则就会出现有一个无效的情况。

那实际的项目举例说明一下:

之前一共两个功能

(1)发送短信客户端,默认配置

(3)发送邮件服务 ,配置了encode。这里邮件是因为要发送附件,所以用到了文件上传。具体的代码可以看之前的文章。

我又添加了第三个服务,(3)文件上传 ,也是配置了专门的encode。这个服务是专门用来上传到阿里云OSS服务的。

我贴出来具体的代码

@ConditionalOnClass(FeignClient.class)
@FeignClient(value = "openservice", configuration = OpenServiceFileFeignService.MultipartSupportEncode.class)
@RequestMapping(value = "/file")
public interface OpenServiceFileFeignService {

    @PostMapping(value = "/upload/file", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    FileUploadResult fileUpload(FileUploadEntity fileUploadEntity);

    @PostMapping(value = "/upload/multipartFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    FileUploadResult multipartFileUpload(MultipartFileUploadEntity multipartFileUploadEntity);

    @PostMapping(value = "/upload/boot1xMultipartFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    FileUploadResult multipartFileUpload(Map<String, Object> multipartFileUploadEntity);

    /**
     * feignclient为了支持文件上传,需要配置SpringFormEncoder
     * 其中的@Component注解是为了在多个同名feignclient时configuration不产生覆盖
     */
    @Component
    public class MultipartSupportEncoder extends SpringFormEncoder {

    @SuppressWarnings("unchecked")
    @Override
    public void encode (Object object, Type bodyType, RequestTemplate template) throws EncodeException {
        if (bodyType.getClass().equals(ParameterizedTypeImpl.class) && ((ParameterizedTypeImpl) bodyType).getRawType().equals(Map.class)) {
            Map<String, Object> data = (Map<String, Object>) object;
            Set<String> nullSet = new HashSet<>();
            for (Map.Entry<String, Object> entry : data.entrySet()) {
                if (entry.getValue() == null) {
                    nullSet.add(entry.getKey());
                }
            }
            for (String s : nullSet) {
                data.remove(s);
            }
            super.encode(data, MAP_STRING_WILDCARD, template);
            return;
        } else if (bodyType.equals(MultipartFile.class)) {
            MultipartFile file = (MultipartFile) object;
            Map<String, Object> data = singletonMap(file.getName(), object);
            super.encode(data, MAP_STRING_WILDCARD, template);
            return;
        } else if (bodyType.equals(MultipartFile[].class)) {
            MultipartFile[] file = (MultipartFile[]) object;
            if (file != null) {
                Map<String, Object> data = singletonMap(file.length == 0 ? "" : file[0].getName(), object);
                super.encode(data, MAP_STRING_WILDCARD, template);
                return;
            }
        }
        super.encode(object, bodyType, template);
    }
}

我按照之前文章的配置,添加@Component之后,测试发现发送邮件是没问题的,但是文件上传服务的这个配置就不行了,还是会一直提示错误信息

{
    "timestamp": 1609246268136,
    "status": 500,
    "error": "Internal Server Error",
    "exception": "feign.codec.EncodeException",
    "message": "Could not write request: no suitable HttpMessageConverter found for request type [org.springframework.web.multipart.commons.CommonsMultipartFile] and content type [multipart/form-data]",
    "path": "/samplefile/fileUpload"
}

问题原因还是重名引发的灾难。

2、解决方案:

没办法,解决方案就是不用注解了,手动创建FeignClient的创建

上代码

(1)首先是写一个创建客户端的类

import feign.Client;
import feign.Contract;
import feign.Feign;
import feign.Logger;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.slf4j.Slf4jLogger;
import lombok.Data;
import org.springframework.cloud.netflix.feign.FeignClientsConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;

/**
 * '@FeignClient的value有重复,导致了多个重名client在加载configuration是会出现覆盖的情况。由于在文件上传相关的client中都
 * 进行了configuration的设置,所以会出现无效的情况。
 * 为了解决这个问题,采用了自定义的方式(官方是叫Feign Builder API)创建feignclient
*/
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
@Data
@Component
@Import(FeignClientsConfiguration.class)
public class FileFeignClientBuilder {

  public OpenServiceFileFeignService openServiceFileFeignService;

  public FileFeignClientBuilder(Decoder decoder, Encoder encoder, Client client, Contract contract) {
    this.openServiceFileFeignService =
        Feign.builder()
            .client(client)
            .encoder(new MultipartSupportEncoder())
            .decoder(decoder)
            .contract(contract)
            // 默认是Logger.NoOpLogger
            .logger(new Slf4jLogger(OpenServiceFileFeignService.class))
            // 默认是Logger.Level.NONE
            .logLevel(Logger.Level.FULL)
//            .requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
            .target(OpenServiceFileFeignService.class, "http://openservice");
  }
}

在上面的代码中可看到,.encoder(new MultipartSupportEncoder()),这个针对这个客户端单独设置了encode的配置。我们这折腾这么就就是为了这个配置可以生效

(2)然后就是去掉@FeignClient的注解

@ConditionalOnClass(FeignClient.class)
//@FeignClient(value = "openservice", configuration = OpenServiceFileFeignService.ExtEncoder.class)
@RequestMapping(value = "/file")
public interface OpenServiceFileFeignService {

    @PostMapping(value = "/upload/file", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    FileUploadResult fileUpload(Map<String, Object> distFileUploadEntity);

(3)最后使用上注意的点

1、扫描包的时候扫描的是FileFeignClientBuilder所在的包

2、自动注入的也是FileFeignClientBuilder

3、要使用feignclient的话是fileFeignClientBulider.getOpenServiceFileFeignService()

@Configuration
@EnableFeignClients({"com.openservice.file.builder"})
@ComponentScan({"com.openservice.file.builder"})
@Import(value = {OpenServiceFileProperties.class})
public class OpenServiceFileConfig {

    @Autowired
    private FileFeignClientBuilder fileFeignClientBulider;

    @Bean
    @ConditionalOnBean(OpenServiceFileProperties.class)
    public OpenServiceFileFeignClient openServiceFileClient(OpenServiceFileProperties fileOpenServiceProperties) {
        return new OpenServiceFileFeignClient(fileFeignClientBulider.getOpenServiceFileFeignService());
    }
}

3、备注:

手动创建feignclient和注解feignclient可以同时存在。我只是把需要单独配置configuration的手动创建。

其他的普通的还是使用@FeignClient注解,正常自动注入使用即可。

4、参考:

手动创建FeignClient参照官方文档

手动创建client



全部评论