1. 新增编码器(由于使用了动态的Feign,所以不能像正常使用Feign一样指定configuration配置编码器)

 import feign.RequestTemplate;
import feign.codec.EncodeException;
import feign.codec.Encoder;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile; import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry; /**
* A custom {@link Encoder} that supports Multipart requests. It uses
* {@link HttpMessageConverter}s like {@link RestTemplate} does.
*
* @author Pierantonio Cangianiello
*/
public class FeignSpringFormEncoder implements Encoder { private final List<HttpMessageConverter<?>> converters = new RestTemplate().getMessageConverters(); public static final Charset UTF_8 = Charset.forName("UTF-8"); public FeignSpringFormEncoder() {
} /**
* {@inheritDoc }
*/
@Override
public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
if (isFormRequest(bodyType)) {
final HttpHeaders multipartHeaders = new HttpHeaders();
multipartHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
encodeMultipartFormRequest((Map<String, ?>) object, multipartHeaders, template);
} else {
final HttpHeaders jsonHeaders = new HttpHeaders();
jsonHeaders.setContentType(MediaType.APPLICATION_JSON);
encodeRequest(object, jsonHeaders, template);
}
} /**
* Encodes the request as a multipart form. It can detect a single {@link MultipartFile}, an
* array of {@link MultipartFile}s, or POJOs (that are converted to JSON).
*
* @param formMap
* @param template
* @throws EncodeException
*/
private void encodeMultipartFormRequest(Map<String, ?> formMap, HttpHeaders multipartHeaders, RequestTemplate template) throws EncodeException {
if (formMap == null) {
throw new EncodeException("Cannot encode request with null form.");
}
LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
for (Entry<String, ?> entry : formMap.entrySet()) {
Object value = entry.getValue();
if (isMultipartFile(value)) {
map.add(entry.getKey(), encodeMultipartFile((MultipartFile) value));
} else if (isMultipartFileArray(value)) {
encodeMultipartFiles(map, entry.getKey(), Arrays.asList((MultipartFile[]) value));
} else {
map.add(entry.getKey(), encodeJsonObject(value));
}
}
encodeRequest(map, multipartHeaders, template);
} private boolean isMultipartFile(Object object) {
return object instanceof MultipartFile;
} private boolean isMultipartFileArray(Object o) {
return o != null && o.getClass().isArray() && MultipartFile.class.isAssignableFrom(o.getClass().getComponentType());
} /**
* Wraps a single {@link MultipartFile} into a {@link HttpEntity} and sets the
* {@code Content-type} header to {@code application/octet-stream}
*
* @param file
* @return
*/
private HttpEntity<?> encodeMultipartFile(MultipartFile file) {
HttpHeaders filePartHeaders = new HttpHeaders();
filePartHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
try {
Resource multipartFileResource = new MultipartFileResource(file.getOriginalFilename(), file.getSize(), file.getInputStream());
return new HttpEntity<>(multipartFileResource, filePartHeaders);
} catch (IOException ex) {
throw new EncodeException("Cannot encode request.", ex);
}
} /**
* Fills the request map with {@link HttpEntity}s containing the given {@link MultipartFile}s.
* Sets the {@code Content-type} header to {@code application/octet-stream} for each file.
*
* @param map current request map.
* @param name the name of the array field in the multipart form.
* @param files
*/
private void encodeMultipartFiles(LinkedMultiValueMap<String, Object> map, String name, List<? extends MultipartFile> files) {
HttpHeaders filePartHeaders = new HttpHeaders();
filePartHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
try {
for (MultipartFile file : files) {
Resource multipartFileResource = new MultipartFileResource(file.getOriginalFilename(), file.getSize(), file.getInputStream());
map.add(name, new HttpEntity<>(multipartFileResource, filePartHeaders));
}
} catch (IOException ex) {
throw new EncodeException("Cannot encode request.", ex);
}
} /**
* Wraps an object into a {@link HttpEntity} and sets the {@code Content-type} header to
* {@code application/json}
*
* @param o
* @return
*/
private HttpEntity<?> encodeJsonObject(Object o) {
HttpHeaders jsonPartHeaders = new HttpHeaders();
jsonPartHeaders.setContentType(MediaType.APPLICATION_JSON);
return new HttpEntity<>(o, jsonPartHeaders);
} /**
* Calls the conversion chain actually used by
* {@link RestTemplate}, filling the body of the request
* template.
*
* @param value
* @param requestHeaders
* @param template
* @throws EncodeException
*/
private void encodeRequest(Object value, HttpHeaders requestHeaders, RequestTemplate template) throws EncodeException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
HttpOutputMessage dummyRequest = new HttpOutputMessageImpl(outputStream, requestHeaders);
try {
Class<?> requestType = value.getClass();
MediaType requestContentType = requestHeaders.getContentType();
for (HttpMessageConverter<?> messageConverter : converters) {
if (messageConverter.canWrite(requestType, requestContentType)) {
((HttpMessageConverter<Object>) messageConverter).write(
value, requestContentType, dummyRequest);
break;
}
}
} catch (IOException ex) {
throw new EncodeException("Cannot encode request.", ex);
}
HttpHeaders headers = dummyRequest.getHeaders();
if (headers != null) {
for (Entry<String, List<String>> entry : headers.entrySet()) {
template.header(entry.getKey(), entry.getValue());
}
}
/*
we should use a template output stream... this will cause issues if files are too big,
since the whole request will be in memory.
*/
template.body(outputStream.toByteArray(), UTF_8);
} /**
* Minimal implementation of {@link HttpOutputMessage}. It's needed to
* provide the request body output stream to
* {@link HttpMessageConverter}s
*/
private class HttpOutputMessageImpl implements HttpOutputMessage { private final OutputStream body;
private final HttpHeaders headers; public HttpOutputMessageImpl(OutputStream body, HttpHeaders headers) {
this.body = body;
this.headers = headers;
} @Override
public OutputStream getBody() throws IOException {
return body;
} @Override
public HttpHeaders getHeaders() {
return headers;
} } /**
* Heuristic check for multipart requests.
*
* @param type
* @return
* @see feign.Types
*/
static boolean isFormRequest(Type type) {
return MAP_STRING_WILDCARD.equals(type);
} /**
* Dummy resource class. Wraps file content and its original name.
*/
static class MultipartFileResource extends InputStreamResource { private final String filename;
private final long size; public MultipartFileResource(String filename, long size, InputStream inputStream) {
super(inputStream);
this.size = size;
this.filename = filename;
} @Override
public String getFilename() {
return this.filename;
} @Override
public InputStream getInputStream() throws IOException, IllegalStateException {
return super.getInputStream(); //To change body of generated methods, choose Tools | Templates.
} @Override
public long contentLength() throws IOException {
return size;
} } }

2. 配置文件中配置编码器

feign.client.config.default.encoder=完整包名.FeignSpringFormEncoder

  

3. 新增FeignClientUtil类

import org.springframework.beans.BeansException;
import org.springframework.cloud.openfeign.FeignClientBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component; import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap; @Component
public class FeignClientUtils implements ApplicationContextAware { private static ApplicationContext applicationContext = null;
private static final Map<String, Object> BEAN_CACHE = new ConcurrentHashMap<>(); @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (FeignClientUtils.applicationContext == null) {
FeignClientUtils.applicationContext = applicationContext;
}
} public static <T> T build(String serverName, Class<T> targetClass) {
return buildClient(serverName, targetClass);
} @SuppressWarnings("unchecked")
private static <T> T buildClient(String serverName, Class<T> targetClass) {
T t = (T) BEAN_CACHE.get(serverName);
if (Objects.isNull(t)) {
FeignClientBuilder.Builder<T> builder = new FeignClientBuilder(applicationContext).forType(targetClass, serverName); t = builder.build();
BEAN_CACHE.put(serverName, t);
}
return t;
}
}

  

4. 新增interface

import xxx.ApiResult;
import org.springframework.web.bind.annotation.*; import java.util.Map; public interface IFeignClientTemplate { @GetMapping(value = "/{url}", headers = {"Content-Type: application/json"})
ApiResult get(@PathVariable("url") String url, @RequestParam Map<String, Object> params); @PostMapping(value = "/{url}", headers = {"Content-Type: application/json"}, consumes = "application/json")
ApiResult post(@PathVariable("url") String url, Map<String, Object> params); @PostMapping(value = "/{url}")
ApiResult postFile(@PathVariable("url") String url, Map<String, ?> params);
}

  

5. 使用实例

String serviceName = "xxx"; // 注册在nacos的服务名称
String url = "xxx"; // 请求的url的接口
DynamicFeignClient client = FeignClientUtils.build(serviceName, DynamicFeignClient.class);
ApiResult result = client.get(url,new HashMap<>());

  

PS:过程中遇到了一些问题,也使用了RequestLine注解,最后都调整与解决了,编码器是必要的,不然无法转换为指定的DTO

如果有人知道怎么在这种情况下使用Configuration配置编码解码注解,也请不吝赐教(试了几个都不行,在build client中指定也不行,没有对应方法)

target values must be absolute.

java.lang.IllegalStateException: Method has too many Body parameters: public abstract java.lang.String xxx.IFeignClientTemplate.get(java.lang.String,java.lang.Object)

feign.FeignException$NotFound: status 404 reading IFeignClientTemplate#get(String)

status 405 reading IFeignClientTemplate#get(String,HashMap)

No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)

等报错

  

Feign 动态设定服务器名称 与 调用接口的更多相关文章

  1. C#动态webservice调用接口 (JAVA,C#)

    C#动态webservice调用接口 using System; using System.Collections; using System.IO; using System.Net; using ...

  2. C#调用接口注意要点 socket,模拟服务器、客户端通信 在ASP.NET Core中构建路由的5种方法

    C#调用接口注意要点   在用C#调用接口的时候,遇到需要通过调用登录接口才能调用其他的接口,因为在其他的接口需要在登录的状态下保存Cookie值才能有权限调用, 所以首先需要通过调用登录接口来保存c ...

  3. SpringCloud:Feign调用接口不稳定问题以及如何设置超时

    1. Feign调用接口不稳定报错 Caused by: java.net.SocketException: Software caused connection abort: recv failed ...

  4. Spring Cloud Feign 在调用接口类上,配置熔断 fallback后,输出异常

    Spring Cloud Feign 在调用接口类上,配置熔断 fallback后,出现请求异常时,会进入熔断处理,但是不会抛出异常信息. 经过以下配置,可以抛出异常: 将原有ErrorEncoder ...

  5. python网络-动态Web服务器案例(30)

    一.浏览器请求HTML页面的过程 了解了HTTP协议和HTML文档,其实就明白了一个Web应用的本质就是: 浏览器发送一个HTTP请求: 服务器收到请求,生成一个HTML文档: 服务器把HTML文档作 ...

  6. Zookeeper动态更新服务器列表

    -------------------------------------------------------------------------------------- [版权申明:本文系作者原创 ...

  7. 【一起学源码-微服务】Feign 源码二:Feign动态代理构造过程

    前言 前情回顾 上一讲主要看了@EnableFeignClients中的registerBeanDefinitions()方法,这里面主要是 将EnableFeignClients注解对应的配置属性注 ...

  8. URLConnection调用接口

    写在前面: 项目是java web,jdk1.4,weblogic 7;对方.net系统,用wcf开发的接口.对方提供接口url地址,以及说明用post方式去调用,无需传递参数,直接返回json ar ...

  9. SpringCloud03 Ribbon知识点、 Feign知识点、利用RestTemplate+Ribbon调用远程服务提供的资源、利用feign调用远程服务提供的资源、熔断

    1 远程服务资源的调用 1.1 古老的套路 在微服务出现之前,所有的远程服务资源必须通过RestTemplate或者HttpClient进行:但是这两者仅仅实现了远程服务资源的调用,并未提供负载均衡实 ...

  10. Spring Cloud 之 Feign 知识点:封装了 REST 调用

    Feign Client 会在底层根据你的注解,跟你指定的服务建立连接.构造请求.发起请求.获取响应.解析响应,等等. Feign 的一个关键机制就是使用了动态代理. 首先,如果你对某个接口定义了 @ ...

随机推荐

  1. P9120 题解

    暴力容斥复活之路! \(k=1\) 这个你肯定会. \(k=2\) 大的放上去,小的放下来.简单贪心. \(k=3\) 考虑二分答案. 然后考虑判断是否合法. 令当前答案为 \(val\). 首先钦定 ...

  2. Java Objects工具类重点方法使用

    Objects工具类 jdk 1.7引进的工具类,都是静态调用的方法,jdk 1.8新增了部分方法 重点方法 equals 用于字符串和包装对象的比较,先比较内存地址,再比较值 deepEquals ...

  3. yb课堂 新版VueCli 4.3创建vue项目,Vue基础语法入门 《二十九》

    Vue模版语法开发起步 基于HTML的模版语法,允许声明式地将DOM绑定至底层Vue实例的数据 用简洁的模版语法来声明式的将数据渲染进DOM的系统 结合响应系统,在应用状态改变时,Vue能够智能地计算 ...

  4. 全网最适合入门的面向对象编程教程:10 类和对象的 Python 实现-类的继承和里氏替换原则,Python 模拟主机和传感器自定义类

    全网最适合入门的面向对象编程教程:10 类和对象的 Python 实现-类的继承和里氏替换原则,Python 模拟主机和传感器自定义类 摘要: 本文主要介绍了类的继承的基本概念和里氏替换原则,以模拟传 ...

  5. OnlyOffice 部署与使用

    Onlyoffice为office类文档预览支持服务,该服务需在Linux系统上运行,需要用Docker容器技术. 以下步骤皆在Linux系统中操作. 一.OnlyOffice部署 1.安装Docke ...

  6. ComfyUI进阶:Comfyroll插件 (三)

    前言: 学习ComfyUI是一场持久战,而Comfyroll 是一款功能强大的自定义节点集合,专为 ComfyUI 用户打造,旨在提供更加丰富和专业的图像生成与编辑工具.借助这些节点,用户可以在静态图 ...

  7. Javascript 转Date对象为字符串实现函数

    转Date对象为字符串实现函数 function formatDate(time, format = "Y-MM-dd HH:mm:ss") { /** 格式化字符说明 Y 年 四 ...

  8. 同时使用mp和mybatis,我的type-aliases-package失效了

    mybatis: #mapper配置文件 mapper-locations: classpath:mapper/*.xml type-aliases-package: com.sky.entity c ...

  9. Java---杂记

    1. 当System.out.println()方法的参数是Object类型时,println()方法会自动调用Object对象的toString()方法,然后显示toString()方法返回的字符串 ...

  10. 13、SpringMVC之异常解析器

    13.1.环境搭建 创建名为spring_mvc_exception的新module,过程参考9.1节和9.5节 13.1.1.创建错误提示页 <!DOCTYPE html> <ht ...