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增加

  1. hystrix:
  2. command:
  3. default:
  4. execution:
  5. timeout:
  6. enabled: false
  7. isolation:
  8. strategy: SEMAPHORE

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

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

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

首先是header封装类


  1. @Data
  2. public class CommonRequestHeader {
  3. /**
  4. * version 版本号
  5. */
  6. private String version;
  7. /**
  8. * 平台类型
  9. */
  10. private String platform;
  11. }

把请求头封装到ThreadLocal中

  1. @Data
  2. public class CommonRequestHeaderHolder {
  3. public static final ThreadLocal<CommonRequestHeader> context = new ThreadLocal<>();
  4. public static void clear() {
  5. context.set(null);
  6. }
  7. public static void setContext(CommonRequestHeader header) {
  8. context.set(header);
  9. }
  10. public static CommonRequestHeader getContext() {
  11. return context.get();
  12. }
  13. }

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

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

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

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

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

  1. if (inheritThreadLocals && parent.inheritableThreadLocals != null)
  2. this.inheritableThreadLocals =
  3. 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 配置到客户端中:

  1. @Configuration
  2. public class FeignClientConfig {
  3. @Bean
  4. Logger.Level feignLoggerLevel() {
  5. return Logger.Level.FULL;
  6. }
  7. }

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

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

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

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

打印Info级别日志

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

  1、重写feign.logger类

  1. public class QjxFeignLogger extends feign.Logger {
  2.  
  3. private final Logger logger;
  4.  
  5. public QjxFeignLogger() {
  6. this(feign.Logger.class);
  7. }
  8.  
  9. public QjxFeignLogger(Class<?> clazz) {
  10. this(LoggerFactory.getLogger(clazz));
  11. }
  12.  
  13. public QjxFeignLogger(String name) {
  14. this(LoggerFactory.getLogger(name));
  15. }
  16.  
  17. QjxFeignLogger(Logger logger) {
  18. this.logger = logger;
  19. }
  20.  
  21. @Override
  22. protected void logRequest(String configKey, Level logLevel, Request request) {
  23. if (logger.isInfoEnabled()) {
  24. super.logRequest(configKey, logLevel, request);
  25. }
  26. }
  27.  
  28. @Override
  29. protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response, long elapsedTime)
  30. throws IOException {
  31. if (logger.isInfoEnabled()) {
  32. return super.logAndRebufferResponse(configKey, logLevel, response, elapsedTime);
  33. }
  34. return response;
  35. }
  36.  
  37. @Override
  38. protected void log(String configKey, String format, Object... args) {
  39. // Not using SLF4J's support for parameterized messages (even though it
  40. // would be more efficient) because it would
  41. // require the incoming message formats to be SLF4J-specific.
  42. if (logger.isInfoEnabled()) {
  43. logger.info(String.format(methodTag(configKey) + format, args));
  44. }
  45. }
  46. }

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

  2、自定义Logger类加入配置

  1. @Configuration
  2. public class FeignClientConfig {
  3. @Bean
  4. Logger.Level feignLoggerLevel() {
  5. return Logger.Level.FULL;
  6. }
  7.  
  8. @Bean
  9. Logger QjxFeign(){
  10. return new QjxFeignLogger();
  11. }
  12. }

  将自定义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. codeforces B. A and B 找规律

    Educational Codeforces Round 78 (Rated for Div. 2) 1278B - 6 B. A and B  time limit per test 1 secon ...

  2. ent 基本使用九 代码生成

    ent 提供了cli 工具,可以方便我们进行schema 以及代码生成,同时目前提供的cli已经够用了 安装 cli go get github.com/facebookincubator/ent/c ...

  3. TCP三次握手的过程,accept发生在三次握手的哪一个阶段?

    答案是:accept过程发生在三次握手之后,三次握手完成后,客户端和服务器就建立了tcp连接并可以进行数据交互了.这时可以调用accept函数获得此连接. TCP Accept总结 TCP Accep ...

  4. 洛谷 P1462 通往奥格瑞玛的道路 题解

    P1462 通往奥格瑞玛的道路 题目背景 在艾泽拉斯大陆上有一位名叫歪嘴哦的神奇术士,他是部落的中坚力量 有一天他醒来后发现自己居然到了联盟的主城暴风城 在被众多联盟的士兵攻击后,他决定逃回自己的家乡 ...

  5. NOI2019 Day1游记

    Day1挂了,没什么好说的... 开场T1想到70分暴力就走人了,后来发现可以写到85...(听说有人写dfs过了95?233333) T2刚了2个多小时,得到每次只在中间填最大值的结论后不会区间DP ...

  6. 【Dubbo】带着问题看源码:什么是SPI机制?Dubbo是如何实现的?

    什么是SPI? ​ 在Java中,SPI全称为 Service Provider Interface,是一种典型的面向接口编程机制.定义通用接口,然后具体实现可以动态替换,和 IoC 有异曲同工之妙. ...

  7. mybatis自定义插件(拦截器)开发详解

    mybatis插件(准确的说应该是around拦截器,因为接口名是interceptor,而且invocation.proceed要自己调用,配置中叫插件)功能非常强大,可以让我们无侵入式的对SQL的 ...

  8. pom.xml activatedProperties --spring.profiles.active=uat 对应

    <profiles> <profile> <id>dev</id> <properties> <!-- 环境标识,需要与配置文件的名称 ...

  9. 【NWJS】解析node-webkit(NWJS)的打包和发布

    目录结构: contents structure [-] 下载和安装node-webkit 建立一个简单的WEB应用 生成EXE可执行文件 修改icon 封包 Enigma Virtual Box I ...

  10. Qt编写项目作品大全(自定义控件+输入法+大屏电子看板+视频监控+楼宇对讲+气体安全等)

    一.自定义控件大全 (一).控件介绍 超过160个精美控件,涵盖了各种仪表盘.进度条.进度球.指南针.曲线图.标尺.温度计.导航条.导航栏,flatui.高亮按钮.滑动选择器.农历等.远超qwt集成的 ...