Feign 动态设定服务器名称 与 调用接口
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 动态设定服务器名称 与 调用接口的更多相关文章
- C#动态webservice调用接口 (JAVA,C#)
C#动态webservice调用接口 using System; using System.Collections; using System.IO; using System.Net; using ...
- C#调用接口注意要点 socket,模拟服务器、客户端通信 在ASP.NET Core中构建路由的5种方法
C#调用接口注意要点 在用C#调用接口的时候,遇到需要通过调用登录接口才能调用其他的接口,因为在其他的接口需要在登录的状态下保存Cookie值才能有权限调用, 所以首先需要通过调用登录接口来保存c ...
- SpringCloud:Feign调用接口不稳定问题以及如何设置超时
1. Feign调用接口不稳定报错 Caused by: java.net.SocketException: Software caused connection abort: recv failed ...
- Spring Cloud Feign 在调用接口类上,配置熔断 fallback后,输出异常
Spring Cloud Feign 在调用接口类上,配置熔断 fallback后,出现请求异常时,会进入熔断处理,但是不会抛出异常信息. 经过以下配置,可以抛出异常: 将原有ErrorEncoder ...
- python网络-动态Web服务器案例(30)
一.浏览器请求HTML页面的过程 了解了HTTP协议和HTML文档,其实就明白了一个Web应用的本质就是: 浏览器发送一个HTTP请求: 服务器收到请求,生成一个HTML文档: 服务器把HTML文档作 ...
- Zookeeper动态更新服务器列表
-------------------------------------------------------------------------------------- [版权申明:本文系作者原创 ...
- 【一起学源码-微服务】Feign 源码二:Feign动态代理构造过程
前言 前情回顾 上一讲主要看了@EnableFeignClients中的registerBeanDefinitions()方法,这里面主要是 将EnableFeignClients注解对应的配置属性注 ...
- URLConnection调用接口
写在前面: 项目是java web,jdk1.4,weblogic 7;对方.net系统,用wcf开发的接口.对方提供接口url地址,以及说明用post方式去调用,无需传递参数,直接返回json ar ...
- SpringCloud03 Ribbon知识点、 Feign知识点、利用RestTemplate+Ribbon调用远程服务提供的资源、利用feign调用远程服务提供的资源、熔断
1 远程服务资源的调用 1.1 古老的套路 在微服务出现之前,所有的远程服务资源必须通过RestTemplate或者HttpClient进行:但是这两者仅仅实现了远程服务资源的调用,并未提供负载均衡实 ...
- Spring Cloud 之 Feign 知识点:封装了 REST 调用
Feign Client 会在底层根据你的注解,跟你指定的服务建立连接.构造请求.发起请求.获取响应.解析响应,等等. Feign 的一个关键机制就是使用了动态代理. 首先,如果你对某个接口定义了 @ ...
随机推荐
- 本地自建KMS服务器
本地自建KMS服务器 本地自建KMS服务器 一.前期准备 下载安装以下软件.文件: VMware Github中的开源项目:vlmcsd 二.在VMware下部署KMS服务器 解压vlmcsd项目中r ...
- position的值, relative和absolute分别是相对于谁进行定位的?
relative: 相对定位,相对于自己本身在正常文档流中的位置进行定位 相对它原来的位置,在走100px.原来在标准流中的位置继续占有. absolute: 生成绝对定位,相对于最近一级定位不为s ...
- 日常工作中需要避免的9个React坏习惯
前言 React是前端开发领域中最受欢迎的JavaScript库之一,但有时候在编写React应用程序时,可能陷入一些不佳的习惯和错误做法.这些不佳的习惯可能导致性能下降.代码难以维护,以及其他问题. ...
- 顺序表_C
// Code file created by C Code Develop #include "ccd.h" #include "stdio.h" #incl ...
- 记hashmap
hashmap是map接口的一个实现类,在同步的情况下hashmap的性能是比较好的 hashmap就是一个kv键值对的集合,将数值散列均匀的存储在哈希表中.插入方法为map.put(k,v),读取方 ...
- STM32F103 SPI详解及示例代码
1 SPI协议详解 SPI是串行外设接口(Serial Peripheral Interface)的缩写,是美国摩托罗拉公司(Motorola)最先推出的一种同步串行传输规范,也是一种单片机外设芯片串 ...
- python 私有属性的作用
python 私有属性的作用 class Player(): def __init__(self, name, power, skill): self.name = name self.power = ...
- [python] 启发式算法库scikit-opt使用指北
scikit-opt是一个封装了多种启发式算法的Python代码库,可以用于解决优化问题.scikit-opt官方仓库见:scikit-opt,scikit-opt官网文档见:scikit-opt-d ...
- 【Binary】XShell6 无法使用的解决办法
感谢博主的解决方案: https://www.cnblogs.com/pinkpolk/articles/13554445.html 首先需要安装VsCode,并且安装一个[Hex Editor]的插 ...
- 【C3】07 盒子模型
在 CSS 中,所有的元素都被一个个的"盒子(box)"包围着, 理解这些"盒子"的基本原理,是我们使用CSS实现准确布局.处理元素排列的关键. 本文围绕 &q ...