Using Feign to send http request and source code analysis

Using Feign to send http request and source code analysis

brief 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.

Annotation

annotation Scope of action usage
@RequestLine Method Define HttpMethod and UriTemplate requests. {expression} parses the bracketed value with its corresponding @ Param annotation parameter.
@Param parameter On the parameter, define a template variable whose value will be used by Expression to resolve the corresponding template by name.
@Headers Method, type Define a variant UriTemplate; of HeaderTemplate. Use the value annotated with @ Param to resolve the corresponding Expressions. Type when used on, the template is applied to each request. When Method is used on, the template will only apply to annotated methods.
@QueryMap parameter Define a Map name value pair or POJO to extend to the query string.
@HeaderMap parameter Define a Map name value pair to extend to Http Headers
@Body Method Define Template, similar to UriTemplate and HeaderTemplate, and use @ Param annotation value to solve corresponding Expressions.

If you need to direct the request to another host, you need the host provided when you create the Feign client, or to provide the target host for each request, include a java.net.URI parameter that Feign will use as the request target.
@RequestLine("POST /repos/{owner}/{repo}/issues")
void createIssue(URI host, Issue issue, @Param("owner") String owner, @Param("repo") String repo);

Demo

ready

idea creates a springboot project (here my project port is set to 8089, which can be set by myself). Check openfeign as shown below:

test

Create controller as service interface

package com.reallinxu.cloud.controller;

import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;

/**
 * @author linxu
 */
@RestController
public class TestController {

    /**
     * demo1 -- @RequestLine @Param Simple use
     *
     * @param param1
     * @return
     */
    @GetMapping("/demo1/{param1}")
    public String demo1(@PathVariable String param1) {
        return "Demo1: hello" + param1;
    }

    /**
     * demo2 -- @Headers Use
     *
     * @param param1
     * @return
     */
    @GetMapping("/demo2/{param1}")
    public String demo2(HttpServletRequest request, @PathVariable String param1) {
        System.out.println(request.getHeader("Accept"));
        return "Demo2: hello" + param1;
    }

    /**
     * demo3 -- @QueryMap Using map
     *
     * @param param1
     * @return
     */
    @GetMapping("/demo3")
    public String demo3(@RequestParam String param1, @RequestParam String param2) {
        return "Demo3: param1-" + param1 + " param2-" + param2;
    }

    /**
     * demo4 -- @QueryMap Using pojo
     *
     * @param param1
     * @return
     */
    @GetMapping("/demo4")
    public String demo4(@RequestParam String param1, @RequestParam String param2) {
        return "Demo4: param1-" + param1 + " param2-" + param2;
    }

    /**
     * demo5 -- @HeaderMap Use
     *
     * @param request
     * @return
     */
    @GetMapping("/demo5")
    public String demo5(HttpServletRequest request) {
        System.out.println(request.getHeader("Accept"));
        System.out.println(request.getHeader("accept-encoding"));
        return "Demo5: hello ";
    }

    /**
     * demo6 -- @Body Use
     *
     * @param param
     * @return
     */
    @PostMapping("/demo6")
    public String demo6(@RequestBody String param) {
        return "Demo6: param- " + param;
    }

}

Create an interface to use as a feign client

package com.reallinxu.cloud.feign;


import com.reallinxu.cloud.entity.Demo4;
import feign.*;

import java.net.URI;
import java.util.Map;

/**
 * feign Interface
 *
 * @author linxu
 */
public interface TestInterface {

    /**
     * demo1 -- Basic Usage
     *
     * @param param1
     * @return
     */
    @RequestLine("GET /demo1/{param1}")
    String demo1(@Param("param1") String param1);

    /**
     * demo2 -- @Headers Use
     *
     * @param param1
     * @param param2
     * @return
     */
    @RequestLine("GET /demo2/{param1}")
    @Headers("Accept: {param2}")
    String demo2(@Param("param1") String param1, @Param("param2") String param2);

    /**
     * demo3 -- @QueryMap Using map
     *
     * @param paramMap
     * @return
     */
    @RequestLine("GET /demo3")
    String demo3(@QueryMap Map paramMap);

    /**
     * demo4 -- @QueryMap Using pojo
     *
     * @param demo4
     * @return
     */
    @RequestLine("GET /demo4")
    String demo4(@QueryMap Demo4 demo4);

    /**
     * demo5 -- @HeaderMap Using pojo
     *
     * @param demo5
     * @return
     */
    @RequestLine("GET /demo5")
    String demo5(@HeaderMap Map demo5);

    /**
     * demo6
     */
    @RequestLine("POST /demo6")
    @Headers("Content-Type: application/json")
    @Body("%7B\"param1\": \"{param1}\", \"param2\": \"{param2}\"%7D")
    String demo6(@Param("param1") String param1, @Param("param2") String param2);

    /**
     * demoN -- Custom host
     *
     * @param param1
     * @return
     */
    @RequestLine("GET /demo1/{param1}")
    String demoN(URI host, @Param("param1") String param1);

}

Create a pojo for Demo4

package com.reallinxu.cloud.entity;

public class Demo4 {
    private String param1;

    private String param2;

    public String getParam1() {
        return param1;
    }

    public void setParam1(String param1) {
        this.param1 = param1;
    }

    public String getParam2() {
        return param2;
    }

    public void setParam2(String param2) {
        this.param2 = param2;
    }
}

Create a test case to test

package com.reallinxu.cloud.test;

import com.reallinxu.cloud.entity.Demo4;
import com.reallinxu.cloud.feign.TestInterface;
import feign.Feign;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;

/**
 * Test class
 * @author linxu
 */
public class Test {
    public static void main(String[] args) throws URISyntaxException {
        TestInterface testInterface = Feign.builder()
                        .target(TestInterface.class, "http://localhost:8089");

        //demo1
        System.out.println(testInterface.demo1("demo1 - 1"));

        //demo2
        System.out.println(testInterface.demo2("demo2 - 1", "text/html"));

        //demo3
        Map demo3Map = new HashMap<String,String>();
        demo3Map.put("param1","heihei");
        demo3Map.put("param2","haha");
        System.out.println(testInterface.demo3(demo3Map));

        //demo4
        Demo4 demo4 = new Demo4();
        demo4.setParam1("heihei");
        demo4.setParam2("haha");
        System.out.println(testInterface.demo4(demo4));

        //demo5
        Map demo5 = new HashMap<String,String>();
        demo5.put("Accept","text/html");
        demo5.put("accept-encoding","gzip");
        System.out.println(testInterface.demo5(demo5));

        //demo6
        System.out.println(testInterface.demo6("heihei","haha"));

        //Custom host, port 8081 will be accessed here, connection denied
        URI a = new URI("http://localhost:8081");
        System.out.println(testInterface.demoN(a,"demo2 - 2"));
    }
}

Start the springboot project and run Test to view the execution results.

Source code analysis

Define feign client interface

TestInterface testInterface = Feign.builder().target(TestInterface.class, "http://localhost:8089");

Some parameters can be set when feign.builder(), which are not set as default values. The corresponding parameters are as follows:

//Control log level
//NONE: no record.
//BASIC: only request method and URL, response status code and execution time are recorded.
//HEADERS: Records basic information as well as request and response HEADERS.
//FULL: records the header, body, and metadata of the request and response.
this.logLevel = Level.NONE; 
//Defines comments and values that are valid on the interface. The definition and corresponding processing of @ RequestLine, @ param and other annotations through registerClassAnnotation
this.contract = new Default();
//Http requests the client. Parameters need to be set when calling https
this.client = new feign.Client.Default((SSLSocketFactory)null, (HostnameVerifier)null);
//Retry mechanism, set retry period and number of retries
this.retryer = new feign.Retryer.Default();
//You can set your own logging mode
this.logger = new NoOpLogger();
//It is called when the request parameter is an object. json and String are supported by default
this.encoder = new feign.codec.Encoder.Default();
//Define decoder, call when response is object, turn response to corresponding object
this.decoder = new feign.codec.Decoder.Default();
//If @ QueryMap uses the type of object, it will be converted to Map by encoding
this.queryMapEncoder = new feign.QueryMapEncoder.Default();
//According to the status implementation, type exceptions suitable for the project scenario can be thrown
this.errorDecoder = new feign.codec.ErrorDecoder.Default();
//Control the settings requested by the client, set the connection time, read time, and whether 3xx redirection is supported
this.options = new Options();
//Control the call of reflection method
this.invocationHandlerFactory = new feign.InvocationHandlerFactory.Default();
//Turn off response automatically after decoding
this.closeAfterDecode = true;
//The apache license doesn't allow files or anything, no matter what
this.propagationPolicy = ExceptionPropagationPolicy.NONE;

Next, let's look at the target method. The principle is to generate a Proxy proxy class according to the interface. The main method is build().newInstance(target)

Pay special attention to several parameters in the box. Let's debug them and see what they are

It can be seen that the key in nameToHandler is the method in the interface, and the value is the parameter set when initializing Feign. Note that there is an incoming host address in the target

methodToHandler is also simple and straightforward, obviously for the sake of reflection in the future

default void defaultMethod(){
    System.out.println("I'm the default");
}

We add the above default method to the TestInterface interface and debug to see the default method handlers used to store the interface. The default method needs to be bound to the proxy object.

Call feign client interface

The proxy class has been generated. Next, let's look at the call.

Except for some methods in the object, others are all reflected

Here you can see that the RestTemplate was created to initiate the request and use the configuration set at the time of creation

Creating a RestTemplate converts the expression to the correct connection

Send the request through the client, and the whole request is completed.

end

This is the end of feign's Http call. Feign microservices can also be called through service ID and internally use ribbon for load balancing. If you are interested in updating later, over~

Published 2 original articles, won praise 2, visited 235
Private letter follow

Tags: Java encoding codec SpringBoot

Posted on Wed, 15 Jan 2020 02:40:17 -0800 by Greaser9780