As for how services communicate with services in microservices, I have introduced the remote invocation knowledge of the Ribbon. Have you found any problems with the Ribbon?

The problem of Ribbon

In the Ribbon, if we want to make a call, it looks like this:

@Resource
private RestTemplate restTemplate

String result = restTemplate.getForObject("http://my-goods/goods/get", String.class);
Goods goods = JSONObject.parseObject(result, Goods.class);
Copy the code

This is just like a normal HTTP request, requiring manual handling of incoming and outgoing parameters.

At first glance, this may seem fine, but on second thought, it is not: the interface being called is written by ourselves, the input and output parameters are determined, and even the interface being called is written by the same person… Is there a better way, like calling it directly as if it were a native method?

Like this:

Goods goods = goodsServer.get();
Copy the code

The term is called remote method invocation

Today’s main character, Feign, has implemented this function for us

What is Feign

Feign is a Java to HTTP client binder inspired by Retrofit, JAXRS-2.0, and WebSocket.

Binder? In terms of Feign’s implementation and starting point, Feign does not implement the HTPP client itself and has enhanced it on top of other components’ clients, so let’s get a feel for what Feign brings to the table

The client

  • java.net.URL
  • Apache HTTP
  • OK Http
  • Ribbon
  • Java 11 http2
  • .

specification

  • Feign
  • JAX-RS
  • JAX-RS 2
  • SOAP
  • Spring 4
  • .

codec

  • GSON
  • JAXB
  • Jackson
  • .

other

  • Hystrix
  • SLF4J
  • Mock

More on github: github.com/OpenFeign/f…

The basic use

1. Write CRUDS in goods and services

Small partners can casually find their own project to write, mainly is to make a few interfaces to call

@RestController
@RequestMapping("/goods")
public class GoodsController {

    @GetMapping("/get-goods")
    public Goods getGoods(a){
        return new Goods().setName(The word "apple")
                .setPrice(1.1)
                .setNumber(2);
    }

    @GetMapping("/list")
    public List<Goods> list(a){
        ArrayList<Goods> goodsList = new ArrayList<>();
        Goods apple = new Goods().setName(The word "apple")
                .setPrice(1.1)
                .setNumber(2);
        goodsList.add(apple);
        Goods lemon = new Goods().setName("Lemon")
                .setPrice(5.1)
                .setNumber(3);
        goodsList.add(lemon);
        return goodsList;
    }

    @PostMapping("save")
    public void save(@RequestBody Goods goods){
        System.out.println(goods);
    }

    @DeleteMapping
    public void delete(String id){ System.out.println(id); }}Copy the code

2. Create a demo and introduce dependencies

<dependencies>
  <dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-core</artifactId>
  </dependency>
  <! -- codec -->
  <dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-jackson</artifactId>
  </dependency>
</dependencies>
Copy the code

3. Write feIGN specification interface

public interface GoodsApi {

    @RequestLine("GET /goods/get-goods")
    Goods getGoods(a);

    @RequestLine("GET /goods/list")
    List<Goods> list(a);

    @RequestLine("POST /goods/save")
    @Headers("Content-Type: application/json")
    void save(Goods goods);

    @RequestLine("DELETE /goods? id={id}")
    @Headers("Content-Type: application/json")
    void delete(@Param("id") String id);
}
Copy the code

What is the Feign specification

4. Test

public class FeignDemo {

    public static void main(String[] args) {
        // Build feign interface
        GoodsApi goodsApi = Feign.builder()
                .encoder(new JacksonEncoder())
                .decoder(new JacksonDecoder())
                .target(GoodsApi.class, "http://localhost:8082");
        // Call the test
        System.out.println(goodsApi.getGoods());
        System.out.println(goodsApi.list());
        goodsApi.save(new Goods().setName("banana"));
        goodsApi.delete("1"); }}Copy the code

flashes

See here, I don’t know if you have a flash of inspiration?

If I write my build code like this

@Bean
public GoodsApi goodsApi(a){
  return Feign.builder()
                .encoder(new JacksonEncoder())
                .decoder(new JacksonDecoder())
                .target(GoodsApi.class, "http://localhost:8082");
}
Copy the code

And then you inject it with woC. Isn’t that amazing?

Common usage mode

In addition to the basic usage above, Feign also supports the Spring 4 specification, as well as various HTTP clients (e.g. OkHttp), retry timeout, logging, etc. Let me give you a common way to use Feign

Increased reliance on

<! -- spring4 specification -->
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-spring4</artifactId>
  <version>10.10.1</version>
</dependency>
<! -- Ribbon Client -->
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-ribbon</artifactId>
</dependency>
<! -- okhttp client -->
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-okhttp</artifactId>
</dependency>
<! - log - >
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-slf4j</artifactId>
</dependency>
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
</dependency>
Copy the code

Write the interface

public interface Spring4GoodsApi {

    @GetMapping("/goods/get-goods")
    Goods getGoods(a);

    @GetMapping("/goods/list")
    List<Goods> list(a);

    @PostMapping(value = "/goods/save", consumes = MediaType.APPLICATION_JSON_VALUE)
    void save(Goods goods);

    @DeleteMapping("/goods")
    void delete(@RequestParam(value = "id") String id);
}
Copy the code

test

public class Spring4FeignDemo {

    public static void main(String[] args) {
        Spring4GoodsApi goodsApi = Feign.builder()
                // use the spring4 specification
                .contract(new SpringContract())
                // Use Jackson codec
                .encoder(new JacksonEncoder())
                .decoder(new JacksonDecoder())
                // Okhttp client
                .client(new OkHttpClient())
                // The request fails to be retried. The default maximum is five times
                .retryer(new Retryer.Default())
                // Request timeout configuration
                .options(new Request.Options(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true))
                // Log configuration, will print logs before and after the request
                .logger(new Slf4jLogger())
                // Log level configuration: BASIC: displays only the BASIC information about the request path and response status code
                .logLevel(Logger.Level.BASIC)
                .target(Spring4GoodsApi.class, "http://localhost:8082");
        System.out.println(goodsApi.getGoods());
        System.out.println(goodsApi.list());
        goodsApi.save(new Goods().setName("banana"));
        goodsApi.delete("1"); }}Copy the code

The interceptor

An HTTP client will have an interceptor mechanism to do things uniformly before a request: add headers, for example

Feign’s interceptors are used as follows:

public class AuthFeignInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        System.out.println("Enter interceptor.");
       	HttpServletRequest request =
                ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        template.header("token", request.getHeader("token")); }}Copy the code

Implement RequestInterceptor

Add in build

Feign.builder()
  .requestInterceptor(new AuthFeignInterceptor())
  .target(Spring4GoodsApi.class, "http://localhost:8082");
Copy the code

This is a common Feign approach, and once learned, it’s easy to integrate into Spring Cloud.

Integration of Spring Cloud

IO /spring-clou…

The integration sample invokes the goods service for the order service

Direct three plate axe walk

1. Add dependencies

Introduce dependencies in order-server

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
Copy the code

2. Add notes

Add the annotation EnableFeignClients to the Application class

@EnableFeignClients
@SpringBootApplication
public class OrderApplication {

    public static void main(String[] args) { SpringApplication.run(GoodsApplication.class, args); }}Copy the code

Any configuration class can be used, but it is recommended to add it to the startup class, because the annotation scans all packages in the annotation classpath by default, and the startup class is at the top, so all packages can be scanned.

Of course, you can also scan a package directly, since feign interfaces are usually put together

3. Write configurations

See configuration section.

Three plate axe end, start to write sample

4. Write samples

@FeignClient(name = "my-goods", path = "/goods", contextId = "goods")
public interface GoodsApi {

    @GetMapping("/get-goods")
    Goods getGoods(a);

    /** * get@SpringQueryMapNote * /
    @GetMapping("/goods")
    Goods getGoods(@SpringQueryMap Goods goods);

    @GetMapping("/list")
    List<Goods> list(a);

    @PostMapping(value = "/save")
    void save(Goods goods);

    @DeleteMapping
    void delete(String id);
}
Copy the code

FeignClient:

Name: indicates the name of the invoked service

Path: path prefix. All interfaces of this class inherit this path

contextId: It is used to distinguish different FEIGN interfaces, because generally a service has more than one FEIGN interface. For example, there is a GoodsDetailApi(Product details), but their name attribute is the same, they are both goods and services, so a contextId is needed to distinguish different business scenarios

The others are the same as those in common use

5. Test

@RestController
@RequestMapping("/order")
public class OrderController {
    @Resource
    private GoodsApi goodsApi;

    @GetMapping("/get-goods")
    public Goods getGoods(a){
        returngoodsApi.getGoods(); }}Copy the code

configuration

In the usual way, we build the properties of a Feign interface, which are hardcoded and integrated into Spring, and can be configured to make it more flexible.

The log

feign:
  client:
    config:
      DefaultConfig = defaultConfig = default
      default:
        loggerLevel: FULL
      ContextId has a higher priority
      goods:
        loggerLevel: BASIC
Copy the code

Global configuration here special pit, do not look at the source do not know how to match, small partners must pay attention to

The client

By default, Feign uses HttpURLConnection as the client. You can also use other clients

Introduction: All clients are implementation classes of the Client interface. If you want to know whether the replacement is successful, you only need to make a breakpoint in the corresponding implementation class

Use httpclient

Introduction of depend on

<dependency>
  <groupId>org.apache.httpcomponents</groupId>
  <artifactId>httpclient</artifactId>
</dependency>
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-httpclient</artifactId>
  <version>10.10.1</version>
</dependency>
Copy the code

This is fine without any configuration changes, because when ApacheHttpClient’s class is included in the service, the httpClient feign auto-configuration class takes effect and has a higher priority than the default HttpURLConnection auto-configuration class. At this point, the service will inject HttpClient as the client.

The source code is as follows:

@Import Import order is HttpClient, OkHttp, Default(HttpURLConnection)

This configuration class takes effect only if the ApacheHttpClient class exists, and feign.httpclient.enabled does not take effect by default.

Using OkHttp

Introduction of depend on

<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-okhttp</artifactId>
</dependency>
Copy the code

Since we introduced the httpClient dependency in the previous step, and httpClient has a higher priority than okHTTP and is enabled by default, there are two ways to enable okHTTP:

  • Remove httpClient dependencies
  • The HttpClient is disabled

I’m going to use the second method here

feign:
  # Close httpClient
  httpclient:
    enabled: false
  Turn okhttp on
  okhttp:
    enabled: true
Copy the code

GZIP compression

Sometimes when the request data is too large, compressing the data can effectively improve the request performance. Feign also provides this configuration

Note: Compression only works for non-OKHTTP clients

feign:
  httpclient:
    enabled: true
  # configuration compression
  compression:
    request:
      enabled: true
      These are the default types of compression
      mime-types: text/xml, application/xml, application/json
      # Minimum threshold for bytes to be compressed. The default value is 1024
      min-request-size: 10
Copy the code

I’m using HttpClient here, but I don’t have to write it, but I just want you to know that I’m not using OKHTTP

Fault tolerant retry

One of the problems that Internet applications can’t solve: network partitioning. When the service provider is unable to respond to the situation, it is impossible for us to keep the service consumer waiting, so we can configure a timeout period for the service. After a certain time, the service that is called does not respond, and then the request is blocked.

Feign configuration:

feign:
  client:
    config:
      # global configuration
      default:
        The default connection timeout is 10 seconds
        connectTimeout: 1000
        The default timeout unit is 60 seconds
        readTimeout: 5000
Copy the code

Internet applications can not solve the second problem: network jitter. When experiencing this situation, we can only let the service retry.

Feign configuration:

feign:
  client:
    config:
      # global configuration
      default:
        Retry 5 times by default
        retryer: feign.Retryer.Default
Copy the code

The interceptor

The interceptor is implemented in the same way as the RequestInterceptor interface

@Component
public class AuthFeignInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        System.out.println("Enter interceptor.");
       	HttpServletRequest request =
                ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        template.header("token", request.getHeader("token")); }}Copy the code

Adding the @Component annotation to the Spring container automatically adds the service to feign’s interceptor chain when it starts

You can also use the configuration mode

feign:
  client:
    config:
      # global configuration
      default:
        requestInterceptors:
          - com.my.micro.service.order.interceptor.AuthFeignInterceptor
Copy the code

summary

This article covered a remote method invocation called Feign in detail. Now, let’s take a quick review.

What is Feign?

A remote method invocation client that integrates ribbon,httpclient, okhttp, and supports various specifications such as Spring4

How to use Feign?

Edit interface, plus Feign support specification annotations, using Feign.Builder to build the proxy class and initiate the call.

How to integrate SpringCloud?

Import dependencies, annotated with @enableFeignClients, and add configurations as required

This article has basically covered some of Feign’s common ways, and I hope you found them useful. See you next time

Gittee: gitee.com/lzj960515/m…

Personal blog space: zijiancode. Cn/archives/fe…

If you want to know more exciting content, welcome to pay attention to the public account: programmer AH Jian, AH Jian in the public account welcome your arrival ~