Spring Cloud explains Feign's core principles and performance optimization

1. introduction

Feign is a lightweight framework for HTTP request calling, which can call HTTP request by java interface annotation instead of directly calling by encapsulating HTTP request message in Java. Feign templates the request by processing comments. When it is actually called, it passes in parameters, which are then applied to the request according to the parameters, and then converted into a real request. This kind of request is relatively intuitive.
Feign is widely used in Spring Cloud solutions, and is an indispensable component for learning the microservice architecture based on Spring Cloud.

2. Working principle

  • The @ EnableFeignClients annotation is added to the main program entry to enable scanning and loading of FeignClient. According to the development specification of Feign Client, define the interface and add @ FeignClientd annotation.

  • When the program starts, it will scan the package, scan all @ FeignClients' annotated classes, and inject these information into the Spring IOC container. When the methods in the defined Feign interface are called, the specific RequestTemplate will be generated by JDK dynamic proxy. When a proxy is generated, Feign creates a RequestTemplate for each interface method. When generating an agent, Feign will create a RequestTemplate object for each interface method, and change the object to encapsulate all the information needed for HTTP requests, such as request parameter name, request method and other information, which are determined in this process.

  • Then the RequestTemplate generates the Request, and then hands it to the Client for processing. In this case, the Client can be JDK's native URLConnection,Apache's HttpClient, or OKhttp. Finally, the Client is encapsulated into the LoadBalanceClient class, which combines the call between the Ribbon load balancer services.

2.1 Phase 1. Generation of implementation class based on interface oriented JDK dynamic proxy

When feign is used, the corresponding interface class will be defined, and HTTP related annotations will be used on the interface class to identify HTTP request parameter information, as follows:

interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);

public static class Contributor {
  String login;
  int contributions;

public class MyApp {
  public static void main(String... args) {
    GitHub github = Feign.builder()
                         .decoder(new GsonDecoder())
                         .target(GitHub.class, "https://api.github.com");
    // Fetch and print a list of the contributors to this library.
    List<Contributor> contributors = github.contributors("OpenFeign", "feign");
    for (Contributor contributor : contributors) {
      System.out.println(contributor.login + " (" + contributor.contributions + ")");

At the bottom of Feign, an implementation class is generated based on the interface oriented dynamic proxy method, and the request call is delegated to the dynamic proxy implementation class. The basic principle is as follows:

 public class ReflectiveFeign extends Feign{
  ///Omit some codes
  public <T> T newInstance(Target<T> target) {
    //According to the interface class and Contract protocol parsing mode, parse the methods and annotations on the interface class, and convert them to the internal MethodHandler processing mode
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
      } else if(Util.isDefault(method)) {
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        methodToHandler.put(method, handler);
      } else {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
    InvocationHandler handler = factory.create(target, methodToHandler);
    // Create a dynamic implementation for the interface class based on Proxy.newProxyInstance, and convert all requests to InvocationHandler for processing.
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);

    for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
    return proxy;
  //Omit some codes

2.2 Phase 2. According to the Contract protocol rules, analyze the annotation information of the interface class to the internal representation

Feign defines the conversion protocol as follows:

 * Defines what annotations and values are valid on interfaces.
public interface Contract {

   * Called to parse the methods in the class that are linked to HTTP requests.
   * Pass in the interface definition and parse it into the corresponding method internal metadata representation
   * @param targetType {@link feign.Target#type() type} of the Feign interface.
  // TODO: break this and correct spelling at some point
  List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType);

2.2.1 protocol specification based on Spring MVC spring MVC contract

In the current Spring Cloud microservice solution, in order to reduce the learning cost, some annotations of Spring MVC are used to complete the request protocol analysis, that is to say, writing the client request interface is the same as writing the server code: the client and the server can be agreed through the SDK, and the client only needs to introduce the SDK API published by the server to use the connection oriented API Interface coding mode docking service:

Of course, the current Spring MVC annotations are not fully usable. Some annotations are not supported, such as @ GetMapping,@PutMapping, etc., which only supports @ RequestMapping, etc. in addition, there are some problems with the inheritance of annotations. The specific restrictions are that each version can have some differences. You can refer to the above code implementation, which is relatively simple.

2.3 Phase 3. Dynamically generate Request based on RequestBean

According to the incoming Bean object and annotation information, extract the corresponding value from it to construct the Http Request object:

2.4 Phase 4. Use Encoder to convert Bean into Http message body (message parsing and transcoding logic)

Feign will finally convert the request into an Http message and send it out. The incoming request object will eventually parse into a message body, as shown below:

Feign makes a simple interface definition, abstracting the Encoder and decoder interfaces:

public interface Encoder {
  /** Type literal for {@code Map<String, ?>}, indicating the object to encode is a form. */

   * Converts objects to an appropriate representation in the template.
   *  Convert entity object into message body of Http request
   * @param object   what to encode as the request body.
   * @param bodyType the type the object should be encoded as. {@link #MAP_STRING_WILDCARD}
   *                 indicates form encoding.
   * @param template the request template to populate.
   * @throws EncodeException when encoding failed due to a checked exception.
  void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException;

   * Default implementation of {@code Encoder}.
  class Default implements Encoder {

    public void encode(Object object, Type bodyType, RequestTemplate template) {
      if (bodyType == String.class) {
      } else if (bodyType == byte[].class) {
        template.body((byte[]) object, null);
      } else if (object != null) {
        throw new EncodeException(
            format("%s is not a type supported by this encoder.", object.getClass()));
public interface Decoder {

   * Decodes an http response into an object corresponding to its {@link
   * java.lang.reflect.Method#getGenericReturnType() generic return type}. If you need to wrap
   * exceptions, please do so via {@link DecodeException}.
   *  The Http message body is extracted from the Response, and the message is automatically assembled through the return type declared by the interface class
   * @param response the response to decode 
   * @param type     {@link java.lang.reflect.Method#getGenericReturnType() generic return type} of
   *                 the method corresponding to this {@code response}.
   * @return instance of {@code type}
   * @throws IOException     will be propagated safely to the caller.
   * @throws DecodeException when decoding failed due to a checked exception besides IOException.
   * @throws FeignException  when decoding succeeds, but conveys the operation failed.
  Object decode(Response response, Type type) throws IOException, DecodeException, FeignException;

  /** Default implementation of {@code Decoder}. */
  public class Default extends StringDecoder {

    public Object decode(Response response, Type type) throws IOException {
      if (response.status() == 404) return Util.emptyValueOf(type);
      if (response.body() == null) return null;
      if (byte[].class.equals(type)) {
        return Util.toByteArray(response.body().asInputStream());
      return super.decode(response, type);

At present, Feign has the following implementations:

Encoder/ Decoder implementation Explain
JacksonEncoder,JacksonDecoder Persistent conversion protocol based on Jackson format
GsonEncoder,GsonDecoder Persistent conversion protocol based on Google GSON format
SaxEncoder,SaxDecoder Persistent transformation protocol of Sax Library Based on XML format
JAXBEncoder,JAXBDecoder JAXB library persistent conversion protocol based on XML format
ResponseEntityEncoder,ResponseEntityDecoder Spring MVC conversion protocol based on responseentity < T > return format
SpringEncoder,SpringDecoder The conversion protocol based on a set of mechanism of Spring MVC HttpMessageConverters is applied to the Spring Cloud system

2.5 Phase 5. The interceptor is responsible for decorating the request and return

In the process of request transformation, Feign abstracts the interceptor interface, which is used for user-defined operations on the request:

public interface RequestInterceptor {

   * When building a RequestTemplate request, you can add or modify Header, Method, Body and other information
   * Called for every request. Add data using methods on the supplied {@link RequestTemplate}.
  void apply(RequestTemplate template);

For example, if you want Http messaging to be compressed, you can define a request Interceptor:

public class FeignAcceptGzipEncodingInterceptor extends BaseRequestInterceptor {

     * Creates new instance of {@link FeignAcceptGzipEncodingInterceptor}.
     * @param properties the encoding properties
    protected FeignAcceptGzipEncodingInterceptor(FeignClientEncodingProperties properties) {

     * {@inheritDoc}
    public void apply(RequestTemplate template) {
        //  Add corresponding data information in Header header
        addHeader(template, HttpEncoding.ACCEPT_ENCODING_HEADER, HttpEncoding.GZIP_ENCODING,

2.6 Phase 6. Log

When sending and receiving requests, Feign defines a unified log interface to output log information, and defines four levels of log output:

level Explain
NONE No record
BASIC Only the output Http method name, request URL, return status code and execution time are recorded
HEADERS Record the output Http method name, request URL, return status code, execution time and Header information
FULL Record the Header, Body and some Request metadata of Request and Response
public abstract class Logger {

  protected static String methodTag(String configKey) {
    return new StringBuilder().append('[').append(configKey.substring(0, configKey.indexOf('(')))
        .append("] ").toString();

   * Override to log requests and responses using your own implementation. Messages will be http
   * request and response text.
   * @param configKey value of {@link Feign#configKey(Class, java.lang.reflect.Method)}
   * @param format    {@link java.util.Formatter format string}
   * @param args      arguments applied to {@code format}
  protected abstract void log(String configKey, String format, Object... args);

  protected void logRequest(String configKey, Level logLevel, Request request) {
    log(configKey, "---> %s %s HTTP/1.1", request.method(), request.url());
    if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {

      for (String field : request.headers().keySet()) {
        for (String value : valuesOrEmpty(request.headers(), field)) {
          log(configKey, "%s: %s", field, value);

      int bodyLength = 0;
      if (request.body() != null) {
        bodyLength = request.body().length;
        if (logLevel.ordinal() >= Level.FULL.ordinal()) {
              bodyText =
              request.charset() != null ? new String(request.body(), request.charset()) : null;
          log(configKey, ""); // CRLF
          log(configKey, "%s", bodyText != null ? bodyText : "Binary data");
      log(configKey, "---> END HTTP (%s-byte body)", bodyLength);

  protected void logRetry(String configKey, Level logLevel) {
    log(configKey, "---> RETRYING");

  protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response,
                                            long elapsedTime) throws IOException {
    String reason = response.reason() != null && logLevel.compareTo(Level.NONE) > 0 ?
        " " + response.reason() : "";
    int status = response.status();
    log(configKey, "<--- HTTP/1.1 %s%s (%sms)", status, reason, elapsedTime);
    if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {

      for (String field : response.headers().keySet()) {
        for (String value : valuesOrEmpty(response.headers(), field)) {
          log(configKey, "%s: %s", field, value);

      int bodyLength = 0;
      if (response.body() != null && !(status == 204 || status == 205)) {
        // HTTP 204 No Content "...response MUST NOT include a message-body"
        // HTTP 205 Reset Content "...response MUST NOT include an entity"
        if (logLevel.ordinal() >= Level.FULL.ordinal()) {
          log(configKey, ""); // CRLF
        byte[] bodyData = Util.toByteArray(response.body().asInputStream());
        bodyLength = bodyData.length;
        if (logLevel.ordinal() >= Level.FULL.ordinal() && bodyLength > 0) {
          log(configKey, "%s", decodeOrDefault(bodyData, UTF_8, "Binary data"));
        log(configKey, "<--- END HTTP (%s-byte body)", bodyLength);
        return response.toBuilder().body(bodyData).build();
      } else {
        log(configKey, "<--- END HTTP (%s-byte body)", bodyLength);
    return response;

  protected IOException logIOException(String configKey, Level logLevel, IOException ioe, long elapsedTime) {
    log(configKey, "<--- ERROR %s: %s (%sms)", ioe.getClass().getSimpleName(), ioe.getMessage(),
    if (logLevel.ordinal() >= Level.FULL.ordinal()) {
      StringWriter sw = new StringWriter();
      ioe.printStackTrace(new PrintWriter(sw));
      log(configKey, sw.toString());
      log(configKey, "<--- END ERROR");
    return ioe;

2.7 Phase 7. Send HTTP request based on retrier

Feign has a built-in retrier. When there is an IO exception in the HTTP request, feign will have a maximum number of attempts to send the request. The following is feign core code logic:

final class SynchronousMethodHandler implements MethodHandler {

  // Omit some codes

  public Object invoke(Object[] argv) throws Throwable {
   //Construct Http request according to input parameters.
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    // Clone a retrier
    Retryer retryer = this.retryer.clone();
    // The maximum number of attempts, if there is a result in the middle, return directly
    while (true) {
      try {
        return executeAndDecode(template);
      } catch (RetryableException e) {
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);

The retrier has the following control parameters:

Retry parameter Explain Default value
period The initial retry interval. When the request fails, the retrier will pause the initial interval (thread sleep mode) and then start again, avoiding the strong brush request and wasting performance 100ms
maxPeriod When the request fails continuously, the retry interval will be calculated as follows: long interval = (long) (period * Math.pow(1.5, attempt - 1)); it will be extended in an equal proportion manner, but the maximum interval is maxPeriod. Setting this value can avoid too long execution cycle in case of too many retries 1000ms
maxAttempts max retries 5

2.8 Phase 8. Send Http request

Feign actually sends HTTP requests on behalf of feign.Client:

public interface Client {

   * Executes a request against its {@link Request#url() url} and returns a response.
   *  Execute Http request and return Response
   * @param request safe to replay.
   * @param options options to apply to this request.
   * @return connected response, {@link Response.Body} is absent or unread.
   * @throws IOException on a network error connecting to {@link Request#url()}.
  Response execute(Request request, Options options) throws IOException;

Feign implements feign.Client interface class through java.net.HttpURLConnection of JDK in the default bottom layer. When sending a request, a new HttpURLConnection link will be created, which is why feign's performance is poor by default. We can expand this interface to use Apache HttpClient or OkHttp3 and other high-performance Http clients based on connection pool. Our internal project uses OkHttp3 as the Http client.

The following is the default implementation of Feign for reference:

public static class Default implements Client {

    private final SSLSocketFactory sslContextFactory;
    private final HostnameVerifier hostnameVerifier;

     * Null parameters imply platform defaults.
    public Default(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier) {
      this.sslContextFactory = sslContextFactory;
      this.hostnameVerifier = hostnameVerifier;

    public Response execute(Request request, Options options) throws IOException {
      HttpURLConnection connection = convertAndSend(request, options);
      return convertResponse(connection).toBuilder().request(request).build();

    HttpURLConnection convertAndSend(Request request, Options options) throws IOException {
      final HttpURLConnection
          connection =
          (HttpURLConnection) new URL(request.url()).openConnection();
      if (connection instanceof HttpsURLConnection) {
        HttpsURLConnection sslCon = (HttpsURLConnection) connection;
        if (sslContextFactory != null) {
        if (hostnameVerifier != null) {

      Collection<String> contentEncodingValues = request.headers().get(CONTENT_ENCODING);
          gzipEncodedRequest =
          contentEncodingValues != null && contentEncodingValues.contains(ENCODING_GZIP);
          deflateEncodedRequest =
          contentEncodingValues != null && contentEncodingValues.contains(ENCODING_DEFLATE);

      boolean hasAcceptHeader = false;
      Integer contentLength = null;
      for (String field : request.headers().keySet()) {
        if (field.equalsIgnoreCase("Accept")) {
          hasAcceptHeader = true;
        for (String value : request.headers().get(field)) {
          if (field.equals(CONTENT_LENGTH)) {
            if (!gzipEncodedRequest && !deflateEncodedRequest) {
              contentLength = Integer.valueOf(value);
              connection.addRequestProperty(field, value);
          } else {
            connection.addRequestProperty(field, value);
      // Some servers choke on the default accept string.
      if (!hasAcceptHeader) {
        connection.addRequestProperty("Accept", "*/*");

      if (request.body() != null) {
        if (contentLength != null) {
        } else {
        OutputStream out = connection.getOutputStream();
        if (gzipEncodedRequest) {
          out = new GZIPOutputStream(out);
        } else if (deflateEncodedRequest) {
          out = new DeflaterOutputStream(out);
        try {
        } finally {
          try {
          } catch (IOException suppressed) { // NOPMD
      return connection;

    Response convertResponse(HttpURLConnection connection) throws IOException {
      int status = connection.getResponseCode();
      String reason = connection.getResponseMessage();

      if (status < 0) {
        throw new IOException(format("Invalid status(%s) executing %s %s", status,
            connection.getRequestMethod(), connection.getURL()));

      Map<String, Collection<String>> headers = new LinkedHashMap<String, Collection<String>>();
      for (Map.Entry<String, List<String>> field : connection.getHeaderFields().entrySet()) {
        // response message
        if (field.getKey() != null) {
          headers.put(field.getKey(), field.getValue());

      Integer length = connection.getContentLength();
      if (length == -1) {
        length = null;
      InputStream stream;
      if (status >= 400) {
        stream = connection.getErrorStream();
      } else {
        stream = connection.getInputStream();
      return Response.builder()
              .body(stream, length)

3. Feign performance optimization

Feign's overall framework is very small, and there is little time consumption in the process of processing request conversion and message parsing. What really affects performance is the processing of Http requests. By default, feign uses the HttpURLConnection of JDK, so the overall performance is not high. Performance optimization is needed. Apache httpclient or OKHttp are usually used to join the connection pool technology.

3.1 using Apache httpclient

Related categories:

  • org.springframework.cloud.openfeign.ribbon.HttpClientFeignLoadBalancedConfiguration
  • org.springframework.cloud.openfeign.support.FeignHttpClientProperties

Introduce dependency:

<!-- Http Client Support -->
<!-- Apache Http Client Yes Feign Support -->


### Feign configuration
    # Open Http Client
    enabled: true
    # Maximum connections, default: 200
    max-connections: 200
    # Max route, default: 50
    max-connections-per-route: 50
    # Connection timeout, default: 2000 / MS
    connection-timeout: 2000
    # Lifetime, default: 900L
    time-to-live: 900
    # Time unit of response timeout, default: TimeUnit.SECONDS
#    timeToLiveUnit: SECONDS

Be careful:

The number of request links of Apache httpclient is determined by the maximum number of connections and the maximum number of routes. The maximum number of routes is 2 by default, which must be set here. Otherwise, there will be a large number of thread blocking, waiting to get the http link until the timeout exception (wait timeout).

3.2 using OKHttp

OKHttp is a common HTTP client access tool, which has the following characteristics:

  • It supports SPDY and can merge multiple requests to the same host.
  • Use connection pooling technology to reduce request latency if SPDY is available.
  • Use GZIP compression to reduce the amount of data transferred.
  • Caching responses avoids duplicate network requests.

Related categories:

  • org.springframework.cloud.openfeign.FeignAutoConfiguration.OkHttpFeignConfiguration

Introduce dependency:

<!-- OKHttp Yes Feign Support -->


### Feign configuration
    # Open Http Client or not
    enabled: false
#    # Maximum connections, default: 200
#    max-connections: 200
#    # Max route, default: 50
#    max-connections-per-route: 50
#    # Connection timeout, default: 2000 / MS
#    connection-timeout: 2000
#    # Lifetime, default: 900L
#    time-to-live: 900
#    # Time unit of response timeout, default: TimeUnit.SECONDS
##    timeToLiveUnit: SECONDS
    enabled: true

Configuration class:

 * @Author: Knowing the autumn in the desert
 * @Description: Feign The underlying uses OKHttp to access the configuration
 * @CreateDate: 1:59 PM 2018/10/25
public class FeignClientOkHttpConfiguration {

    public OkHttpClient okHttpClient() {
        return new OkHttpClient.Builder()
                // connection timed out
                .connectTimeout(20, TimeUnit.SECONDS)
                // Response timeout
                .readTimeout(20, TimeUnit.SECONDS)
                // Write timeout
                .writeTimeout(20, TimeUnit.SECONDS)
                // Auto reconnect or not
                // Connection pool
                .connectionPool(new ConnectionPool())


Be careful:

If you find that the configured timeout is invalid, you can add the following configuration, because when reading the timeout configuration, you do not read the configuration parameters of the above okhttp, but read it from the Request. The specific configuration is as follows:

    public Request.Options options(){
        return new Request.Options(60000,60000);

3.3 under to replace tomcat

If tomcat is replaced with undertow, the performance is twice as high as tomcat under Jmeter's pressure test.

pom file:


Configuration item:

    max-http-post-size: 0 
# Set the number of IO threads, which mainly perform non blocking tasks. They are responsible for multiple connections. By default, set one thread per CPU core, and the number is the same as the number of CPU cores
    io-threads: 4
# Block the task thread pool. When a servlet like request blocking operation is performed, inferow will get threads from this thread pool. Its value setting depends on the system load IO threads * 8
    worker-threads: 32
# The following configuration will affect the buffer. These buffer will be used for the IO operation of the server connection, which is similar to the pooled memory management of netty
# The space size of each buffer, the smaller the space, the more fully utilized
    buffer-size: 1024
# The number of buffers allocated in each region, so the pool size is buffer size * buffers per region
#   buffers-per-region: 1024 # This parameter does not need to be written
# Allocated direct memory or not
    direct-buffers: true


Published 8 original articles, won praise 0, visited 6499
Private letter follow

Tags: Spring OkHttp Java Apache

Posted on Fri, 31 Jan 2020 23:51:30 -0800 by watson516