注:文章描述的问题还是只出现在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参照官方文档