Feign 的编码器、解码器和客户端都是支持自定义扩展,可以对请求以及结果和发起请求的过程进行自定义实现,Feign 默认支持 JSON 格式的编码器和解码器,如果希望支持其他的或者自定义格式就需要编写自己的编码器和解码器,如果希望编写自己的编码器,需要实现 feign.codec.Encoder 接口,解码器需要实现 feign.codec.Decoder 接口,示例如下:

自定义编码器和解码器

  • 自定义编码器

    实现的自定义编码器只是输出了需要编码的参数信息,而具体的编码还是使用 JSON 格式,使用了 GsonEncoder 编码器来完成具体的编码工作,参数 object 表示需要进行编码的对象,参数 bodyType 为 object 对象的类型,参数 template 表示的就是请求模板,该方法就是需要实现将参数 object 进行编码并赋值到 template 请求模板中。

    package org.lixue.feignclient;

    import feign.RequestTemplate;

    import feign.codec.EncodeException;

    import feign.codec.Encoder;

    import feign.gson.GsonEncoder;

    import java.lang.reflect.Type;

    public class MyEncoder implements Encoder{

    private GsonEncoder gsonEncoder;

    publicMyEncoder(){

    gsonEncoder new GsonEncoder();

    }

    public void encode(Object object,Type bodyType,RequestTemplate template) throws EncodeException{

    System.out.println("encode object is class"+object.getClass().getName());

    System.out.println("encode object is value"+object);

    System.out.println("encode bodyType is class"+bodyType.getClass().getName());

    System.out.println("encode bodyType is value"+bodyType);

    gsonEncoder.encode(object,bodyType,template);

    }

    }

  • 自定义解码器

    实现的自定义解码器使用了 GsonDecoder 解码器来完成具体的编码工作,解码器相对简单,只需要从响应中获取响应报文,然后按照指定的编码格式相应的解码并创建指定的类型实例即可。

    package org.lixue.feignclient;

    import feign.FeignException;

    import feign.Response;

    import feign.codec.DecodeException;

    import feign.codec.Decoder;

    import feign.gson.GsonDecoder;

    import java.io.IOException;

    import java.lang.reflect.Method;

    import java.lang.reflect.Type;

    public class MyDecoder implements Decoder{

    private GsonDecoder gsonDecoder;

    publicMyDecoder(){

    gsonDecoder=newGsonDecoder();

    }

    public Object decode(Response response,Type type)throws IOException,DecodeException,FeignException{

    return gsonDecoder.decode(response,type);

    }

    }

  • 测试验证

    在完成自定义编码器和解码器的开发后,只需要在 Feign 的 builder 方法中,增加解码器和编码器即可,需要注意的是,如果方法请求参数或返回的不是对象,不需要进行编码或解码,就不能增加编码器或解码器,示例代码如下:

    package org.lixue.feignclient;

    import feign.Feign;

    import feign.Logger;

    import feign.gson.GsonDecoder;

    import feign.gson.GsonEncoder;

    public class Startup{

    public static void main(String[]args){

    HelloWorldClient speakClient=

    Feign.builder().target(HelloWorldClient.class,"http://localhost:8080/");

    // 参数和返回都不是对象,不需要附加编码器和解码器

    System.out.println(speakClient.speak("isbody"));

    HelloWorldClient findByIdClient=

    Feign.builder().decoder(new GsonDecoder())

    .target(HelloWorldClient.class,"http://localhost:8080/");

    // 返回的是对象,需要附加解码器

    Person person=findByIdClient.findById(34);

    System.out.println("personid="+person.getId()+"age="+person.getAge()+"name="+person.getName()+"message="+person.getMessage());

    HelloWorldClient createClient=

    Feign.builder().client(newMyClient())

    .decoder(newMyDecoder())

    .encoder(newMyEncoder())

    .target(HelloWorldClient.class,"http://localhost:8080/");

    Person newPerson=new Person();

    newPerson.setId(3434);

    newPerson.setAge(34);

    newPerson.setName("343434");

    newPerson.setMessage("33333333333333333");

    // 参数和返回都是对象,需要附加解码器和编码器

    ReturnValuereturnValue=createClient.create(newPerson);

    System.out.println(returnValue.parseString());

    }

    }

自定义 Feign 客户端

  • 自定义 Feign 客户端

    Feign 使用一个 feign.Client 接口来发送请求,默认实现是使用 HttpURLConnection 连接 HTTP 服务,我们可以实现 feign.Client 接口来完成自定义 Feign 客户端的开发,该接口只有一个方法 execute ,用于执行请求,下面实现了一个自定义的 Feign 客户端,主要完成了请求的日志记录,示例代码如下:

    package org.lixue.feignclient;

    import feign.Client;

    import feign.Request;

    import feign.Response;

    import java.io.IOException;

    import java.util.Collection;

    import java.util.Map;

    public class MyClient implements Client{

    public Response execute(Request request,Request.Options options)throws IOException{

    System.out.println("execute request method="+request.method());

    System.out.println("execute request headers");

    Map<String,Collection<String>> headers=request.headers();

    for(Map.Entry<String,Collection<String>> entry:headers.entrySet()){

    StringBuilderstringBuilder=newStringBuilder();

    for(intj=0;j<entry.getValue().size();j++){

    if(stringBuilder.length()>0){

    stringBuilder.append(",");

    }

    stringBuilder.append(entry.getValue());

    }

    System.out.println(entry.getKey()+":"+stringBuilder.toString());

    }

    byte[] body=request.body();

    if(body!=null){

    System.out.println("execute request body="+newString(body));

    }

    // 使用 Feign 默认的客户端请求

    return new Client.Default(null,null).execute(request,options);

    }

    }

测试验证

和附加编码器、解码器类似,只需要在 Feign 的 builder 方法中附加自定义的客户端即可,代码如下:

package org.lixue.feignclient;

import feign.Feign;

import feign.Logger;

import feign.gson.GsonDecoder;

import feign.gson.GsonEncoder;

public class Startup{

public static void main(String[]args){

HelloWorldClient findByIdClient=

Feign.builder().client(new MyClient())

.decoder(new GsonDecoder())

.target(HelloWorldClient.class,"http://localhost:8080/");

Person person=findByIdClient.findById(34);

System.out.println("personid="+person.getId()+"age="+person.getAge()+"name="+person.getName()+"message="+person.getMessage());

}

}

Feign 转发请求头(header参数)

在做接口请求时,我们经常会在header头中增加一些鉴权信息,如token 或 jwt,那么在通过fegin从A server去调用B server的接口时,如果B server的接口需要header信息,我们需要将A sever获取的header转发到B上。

解决方式

我们需要实现Feign提供的一个接口RequestInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Configuration
public class FeignConfiguration implements RequestInterceptor{
    private final Logger logger = LoggerFactory.getLogger(getClass());
 
            @Override
            public void apply(RequestTemplate template) {
                ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
                        .getRequestAttributes();
                HttpServletRequest request = attributes.getRequest();
                Enumeration<String> headerNames = request.getHeaderNames();
                if (headerNames != null) {
                    while (headerNames.hasMoreElements()) {
                        String name = headerNames.nextElement();
                        String values = request.getHeader(name);
                        template.header(name, values);
 
                    }
                    logger.info("feign interceptor header:{}",template);
                }
               /* Enumeration<String> bodyNames = request.getParameterNames();
                StringBuffer body =new StringBuffer();
                if (bodyNames != null) {
                    while (bodyNames.hasMoreElements()) {
                        String name = bodyNames.nextElement();
                        String values = request.getParameter(name);
                        body.append(name).append("=").append(values).append("&");
                    }
                }
                if(body.length()!=0) {
                    body.deleteCharAt(body.length()-1);
                    template.body(body.toString());
                    //logger.info("feign interceptor body:{}",body.toString());
                }*/
            }
        }

@FeignClient注解里面的属性加上configuration = FeignConfiguration.class就可以了。如

1
2
3
@FeignClient(name = "a-server",  configuration = FeignConfiguration.class)
public interface AServer{
}

bootstrap.yml增加

hystrix:
command:
default:
execution:
timeout:
enabled: false
isolation:
strategy: SEMAPHORE

Feign调用开启Hystrix时无法获取ThreadLocal

在项目中使用根据请求头处理异常信息国际化的问题,但是在feign调用的时候无法传递请求头,这个问题看了好久最后才知道feign开启hystrix默认会新建一个线程,而我的请求头数据是通过拦截器放到ThreadLocal里的在新线程就无法获取了

先看一下原来是怎么实现的

首先是header封装类


@Data
public class CommonRequestHeader { /**
* version 版本号
*/
private String version; /**
* 平台类型
*/
private String platform;
}

把请求头封装到ThreadLocal中

@Data
public class CommonRequestHeaderHolder { public static final ThreadLocal<CommonRequestHeader> context = new ThreadLocal<>(); public static void clear() {
context.set(null);
} public static void setContext(CommonRequestHeader header) {
context.set(header);
} public static CommonRequestHeader getContext() {
return context.get();
}
}

每次请求的时候通过filter封装把请求头数据放入context中

但是在feign中开启hystrix的话新线程的ThreadLocal是无法获取主线程的数据的,这个时候就要用到InheritableThreadLocal,只需要改一行代码

private static final ThreadLocal<CommonRequestData> context = new InheritableThreadLocal<>();

InheritableThreadLocal是ThreadLocal的子类,可以解决父线程和子线程的数据传输问题

当在主线程开启一个新线程时,会执行Thread的init方法
init方法中有这么一段代码

        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

当父线程中的inheritableThreadLocal被赋值时,会将当前线程的inheritableThreadLocal变量进行createInheritedMap(),看一下这个方法的具体实现,它会继续调用ThreadLocalMap(parentMap),主要的目的是父线程的变量值赋值给子线程。

当读取ThreadLocal的地方是个线程池的时候,inheritableThreadLocal也会有问题
 
 

背景

  spring cloud netfix组件中,feign相关的日志默认是不会输出的,需要自定义配置才能输出,并且Feign只对Debug基本的日志做出响应, 实际业务需要输出Info级别的日志,所以需要做自定义配置,覆盖相关配置Bean。

Feign配置

  Feign客户端可以配置各种的Logger.Level对象,告诉Feign记录哪些日志。Logger.Level的值有以下选择。

    NONE,无记录(DEFAULT)。
    BASIC,只记录请求方法和URL以及响应状态代码和执行时间。
    HEADERS,记录基本信息以及请求和响应标头。
    FULL,记录请求和响应的头文件,正文和元数据。

打印Feign日志

  1、Feign的配置类

    根据Feign配置的描述,需要将Logger.Level 配置到客户端中:

 @Configuration
public class FeignClientConfig {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}

  2、在客户端中修改@FeignClient注解

  @FeignClient(name = "qex-comsrv", fallback = ComsrvHystrix.class, configuration = { FeignClientConfig.class })
public abstract interface ComsrvFeignApi{
@RequestMapping({"/otp/esgMsg/send.do"})
public abstract JSONObject send(OTPRequest otprequest); }

  3、修改客户端的日志打印级别

    因为feign只对日志级别为debug级别做出响应,所以如果需要打印出日志,还需要修改客户端的日志级别在application.properties中要设定一行这样的配置:
        logging.level.<你的feign client全路径类名>: DEBUG
    操作完成这三步骤,当调用Send 方法的时候就会打印出Debug级别的日志。

打印Info级别日志

  在实际生产环境中,我们常常需要使用Info级别日志,使用上述针对对每个客户端的配置进行修改,那样将会有大量的配置。所以,需要将修改Feign的日志,对Info级别进行相应。

  1、重写feign.logger类

public class QjxFeignLogger extends feign.Logger {

        private final Logger logger;

        public QjxFeignLogger() {
this(feign.Logger.class);
} public QjxFeignLogger(Class<?> clazz) {
this(LoggerFactory.getLogger(clazz));
} public QjxFeignLogger(String name) {
this(LoggerFactory.getLogger(name));
} QjxFeignLogger(Logger logger) {
this.logger = logger;
} @Override
protected void logRequest(String configKey, Level logLevel, Request request) {
if (logger.isInfoEnabled()) {
super.logRequest(configKey, logLevel, request);
}
} @Override
protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response, long elapsedTime)
throws IOException {
if (logger.isInfoEnabled()) {
return super.logAndRebufferResponse(configKey, logLevel, response, elapsedTime);
}
return response;
} @Override
protected void log(String configKey, String format, Object... args) {
// Not using SLF4J's support for parameterized messages (even though it
// would be more efficient) because it would
// require the incoming message formats to be SLF4J-specific.
if (logger.isInfoEnabled()) {
logger.info(String.format(methodTag(configKey) + format, args));
}
}
}

  自定义一个Logger类,继承Feign.Logger,将代码中的Debug修改成为Info

  2、自定义Logger类加入配置

 @Configuration
public class FeignClientConfig {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
} @Bean
Logger QjxFeign(){
return new QjxFeignLogger();
}
}

  将自定义Logger类加入到Feign客户端配置的Config中,这样Feign系统间调用就能打印出Info级别的日志。

总结

  打印出Feign系统间调用Info级别的日志,核心的思想是Spring Boot项目中,能够自定义配置,自定义的配置优先级大于默认的配置。详情参见Spring Boot自定义配置

Feign 自定义编码器、解码器和客户端,Feign 转发请求头(header参数)、Feign输出Info级别日志的更多相关文章

  1. Feign 自定义编码器、解码器和客户端

    Feign 的编码器.解码器和客户端都是支持自定义扩展,可以对请求以及结果和发起请求的过程进行自定义实现,Feign 默认支持 JSON 格式的编码器和解码器,如果希望支持其他的或者自定义格式就需要编 ...

  2. Feign输出Info级别日志

    背景 spring cloud netfix组件中,feign相关的日志默认是不会输出的,需要自定义配置才能输出,并且Feign只对Debug基本的日志做出响应, 实际业务需要输出Info级别的日志, ...

  3. 解决通过Nginx转发的服务请求头header中含有下划线的key,其值取不到的问题

    1. 问题 由于在http请求头的头部中设置了一些自定义字段,刚好这些字段中含有下划线,比如bundle_name这种,后端在进去获取头部信息时,发现取不到对应的值 2. 原因及解决办法 分析 首先看 ...

  4. ajax请求携带cookie和自定义请求头header

    参考链接:https://blog.csdn.net/menghuanzhiming/article/details/102736312

  5. java nginx等代理或网关转发请求后获取客户端的ip地址,原理

    在没有网关或者反向代理软件情况下,java里获取客户端ip地址的方法是request.getRemoteAddr() 先解释下http协议和TCP协议: 网页默认是进行http连接了,http协议即超 ...

  6. Feign自定义编程配置

    介绍 在Spring Cloud中,Feign的默认配置类是FeignClientsConfiguration,该类定义了Feigh默认使用的编码器.解码器.所使用的契约等.Spring Cloud允 ...

  7. Spring Cloud Feign 自定义配置(重试、拦截与错误码处理) 实践

    Spring Cloud Feign 自定义配置(重试.拦截与错误码处理) 实践 目录 Spring Cloud Feign 自定义配置(重试.拦截与错误码处理) 实践 引子 FeignClient的 ...

  8. Netty学习笔记(三) 自定义编码器

    编写一个网络应用程序需要实现某种编解码器,编解码器的作用就是讲原始字节数据与自定义的消息对象进行互转.网络中都是以字节码的数据形式来传输数据的,服务器编码数据后发送到客户端,客户端需要对数据进行解码, ...

  9. python之simplejson,Python版的简单、 快速、 可扩展 JSON 编码器/解码器

    python之simplejson,Python版的简单. 快速. 可扩展 JSON 编码器/解码器 simplejson Python版的简单. 快速. 可扩展 JSON 编码器/解码器 编码基本的 ...

随机推荐

  1. rhce备战笔记

    1)配置selinuxvim /etc/slinux/config    SELINUX=enforcingsetenforce 1getenforce两台都做 2)配置SSHvim /etc/ssh ...

  2. 洛谷 P2577 [ZJOI2005]午餐 题解

    每日一题 day56 打卡 Analysis 算法:贪心+dp 容易想到贪心:吃饭慢的先打饭节约时间, 所以先将人按吃饭时间从大到小排序. 然后就是dp了: 首先,应该想到f[i][j][k]:前i个 ...

  3. haproxy 2.0 dataplaneapi 类似的工具haproxyadmin

    haproxyadmin 是一个python 的pip 包,提供了类似dataplaneapi 的功能,使用上也比较简单,同时提供的方法也比较全 使用的技术与dataplaneapi 基本类似,也是一 ...

  4. There is insufficient memory for the Java Runtime Environment to continue.

    There is insufficient memory for the Java Runtime Environment to continue.

  5. 4-网页,网站,微信公众号基础入门(配置网站--下载安装PHP)

    https://www.cnblogs.com/yangfengwu/p/10979101.html 这一节咱看一下如何在原先的基础上实现网站 首先去下载 PHP https://windows.ph ...

  6. codeblock代码片段

    for循环 ; $(index name)<| ; $(index name)++ ){ } 2. 反向for循环 ; $(index name)>= ; $(index name)-- ...

  7. 服务器使用ssh秘钥登录并禁止密码登录

    问题: 最近在登录服务器的时候,每次都会有提示999+ falied login等字眼,意思就是自己的服务器密码正在被人暴力破解.想象以下,别人有了你的服务器的root登录密码,那么就可以对你的服务器 ...

  8. linux命令之------rm命令

    rm命令 1)    作用:用于删除一个文件或者目录: 2)    -i:删除前逐一询问确认: 3)    -f:即使原档案属性设为只读,亦直接删除,无需逐一确认: 4)-r:将目录及以下之档案亦逐一 ...

  9. zabbix监控ESXI主机

    ESXI6.0默认SSH关闭的,打开SSH的方法如下图: SSH打开后,主机会有警报,关闭警报的方法如下图 esxcli system  snmp  set  --communities  publi ...

  10. 如何查看电脑的GPU信息

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/qq_33690342/article/ ...