[Microservice] Feign remote call

Let's first look at the code that used RestTemplate to initiate remote calls:

// 2.1 url address
String url = "http://user-service/user/" + order.getUserId();
// 2.2 Initiate a call
User user = restTemplate.getForObject(url, User.class); // GET request, automatically deserialize json into object

The following problems exist:

• Poor code readability and inconsistent programming experience

• Complex URL s with parameters are difficult to maintain

Feign is a declarative http client, official address: https://github.com/OpenFeign/feign

Its role is to help us elegantly implement the sending of http requests and solve the problems mentioned above.

7.1 Feign replaces RestTemplate

The steps to use Fegin are as follows:

7.1.1 Introducing dependencies

Introduce the dependency of feign in the pom file of the order-service service:

<!-- feign client dependent -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

7.1.2 Adding annotations

Add the @EnableFeignClients annotation to the startup class of order-service to enable Feign's function:

@EnableFeignClients
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }

    /**
     * Create RestTemplate and inject into Spring container
     *
     * @return
     */
    @Bean
    @LoadBalanced // load balancing annotations
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}

7.1.3 Write Feign client

Create a new interface in order-service with the following contents:

package cn.itcast.order.client;

import cn.itcast.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient("userservice")
public interface UserClient {
    @GetMapping("/user/{id}")
    User findById(@PathVariable Long id);
}

This client is mainly based on SpringMVC annotations to declare information about remote calls, such as:

  • Service name: userservice
  • Request method: GET
  • Request path: /user/{id}
  • Request parameter: Long id
  • Return value type: User

In this way, Feign can help us send http requests without using RestTemplate ourselves.

7.1.4 Testing

Modify the queryOrderById method in the OrderService class in order-service and use Feign client instead of RestTemplate

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    //@Autowired
    //private RestTemplate restTemplate;

    @Autowired
    private UserClient userClient;

    public Order queryOrderById(Long orderId) {
        // 1 Check order
        Order order = orderMapper.findById(orderId);
        // 2 Remote query user
        // 2.1 url address
        //String url = "http://localhost:8081/user/" + order.getUserId();
        //String url = "http://userservice/user/" + order.getUserId();
        // 2.2 Initiate a call
        //User user = restTemplate.getForObject(url, User.class); // GET request, automatically deserialize json into object
        User user = userClient.findById(order.getUserId()); // Use Feign to initiate http requests to query users
        //3 Deposit to order
        order.setUser(user);
        // 4 Back
        return order;
    }
}

browser access interface

It is necessary to ensure that the two microservices are in the same namespace. Feign has integrated Ribbon to automatically achieve load balancing

7.1.5 Summary

Steps to use Feign:

① Introduce dependencies

② Add @EnableFeignClients annotation

③ Write the FeignClient interface

④ Use the method defined in FeignClient instead of RestTemplate

7.2 Custom configuration

Feign can support many custom configurations, as shown in the following table:

typeeffectillustrate
feign.Logger.LevelChange log levelContains four different levels: NONE (default), BASIC, HEADERS, FULL
feign.codec.DecoderThe parser of the response resultParse the result of http remote call, such as parsing json string into java object
feign.codec.Encoderrequest parameter encodingEncode request parameters for easy sending via http requests
feign. ContractSupported annotation formatsThe default is SpringMVC annotations
feign. Retryerfailure retry mechanismThe retry mechanism for request failure, the default is no, but Ribbon's retry will be used

In general, the default value is sufficient for use. If you want to customize it, you only need to create a custom @Bean to override the default Bean.

The following uses logs as an example to demonstrate how to customize the configuration.

7.2.1 Configuration file method

Modifying feign's log level based on the configuration file can be specific to a single service:

feign:  
  client:
    config: 
      userservice: # Configuration for a Microservice
        loggerLevel: FULL #  log level 

Also for all services:

feign:  
  client:
    config: 
      default: # Here, default is the global configuration. If the service name is written, it is the configuration for a microservice.
        loggerLevel: FULL #  log level 

There are four levels of logging:

  • NONE: Do not log any log information, this is the default value.
  • BASIC: Only log the requested method, URL, and response status code and execution time
  • HEADERS: On the basis of BASIC, additionally record the header information of the request and response
  • FULL: Record details of all requests and responses, including header information, request body, and metadata.

7.2.2 Java Code Mode

You can also modify the log level based on Java code, first declare a class, and then declare an object of Logger.Level:

package cn.itcast.order.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;

public class DefaultFeignConfiguration {
    @Bean
    public Logger.Level logLevel() {
        return Logger.Level.BASIC;  // Log level is BASIC
    }
}

If you want to take effect globally, put it in the @EnableFeignClients annotation of the startup class:

@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class) 

If it takes effect locally, put it in the corresponding @FeignClient annotation:

@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration .class) 

7.3 Feign usage optimization

Feign initiates http requests at the bottom and depends on other frameworks. Its underlying client implementation includes:

• URLConnection: default implementation, does not support connection pooling

• Apache HttpClient: supports connection pooling

• OKHttp: supports connection pooling

So optimizing Feign's performanceism includes:

  1. Use a connection pool instead of the default URLConnection
  2. Log level, preferably basic or none

Here we use Apache's HttpClient to demonstrate.

1) Introduce dependencies

Introduce Apache's HttpClient dependency in the pom file of order-service:

<!--httpClient dependency -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>

2) Configure the connection pool

Add configuration in application.yml of order-service:

feign:
  client:
    config:
      default: # default global configuration
        loggerLevel: BASIC # Log level, BASIC is the basic request and response information
  httpclient:
    enabled: true # Enable feign's support for HttpClient
    max-connections: 200 # maximum number of connections
    max-connections-per-route: 50 # Maximum number of connections per path

7.4 Best Practices

The so-called best practice is the experience summed up in the use process, the best way to use it.

Self-study observation can find that Feign's client is very similar to the service provider's controller code:

feign client:

UserController:

Is there a way to simplify this repetitive code writing?

Method 1 (inheritance): Define a unified parent interface as a standard for the consumer's FeignClient and the provider's controller.

Method 2 (extraction): Extract FeignClient as an independent module, and put the interface-related POJO and default Feign configuration into this module, and provide it to all consumers.

7.4.1 Inheritance

The same code can be shared through inheritance:

1) Define an API interface, use the definition method, and make a declaration based on SpringMVC annotations.

2) Both Feign client and Controller integrate this interface

advantage:

  • Simple
  • code sharing

shortcoming:

  • Service provider and service consumer are tightly coupled

  • The annotation mapping in the parameter list will not be inherited, so the method, parameter list, and annotation must be declared again in the Controller

7.4.2 Extraction method

Extract Feign's Client as an independent module, and put the interface-related POJO and default Feign configuration into this module, and provide it to all consumers.

For example, the default configurations of UserClient, User, and Feign are extracted into a feign-api package, and all microservices can use this dependency package directly.

advantage:

  • convenient
  • low coupling

shortcoming:

  • may have introduced redundant methods

7.4.3 Implementing extraction-based best practices

1) Extraction

First create a module named feign-api:

Introduce feign's starter dependency in feign-api

<!-- feign client dependent -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

Then, the UserClient, User, and DefaultFeignConfiguration written in order-service are copied to the feign-api project

2) Use feign-api in order-service

First, delete classes or interfaces such as UserClient, User, and DefaultFeignConfiguration in order-service.

Introduce the dependency of feign-api in the pom file of order-service:

<!--introduce feign unity api-->
<dependency>
    <groupId>cn.itcast.demo</groupId>
    <artifactId>feign-api</artifactId>
    <version>1.0</version>
</dependency>

Modify all the import packages related to the above three components in order-service to import the packages in feign-api

3) Restart the test

After restarting, it was found that the service reported an error:

Description:

Field userClient in cn.itcast.order.service.OrderService required a bean of type 'cn.itcast.feign.client.UserClient' that could not be found.

The injection point has the following annotations:
	- @org.springframework.beans.factory.annotation.Autowired(required=true)


Action:

Consider defining a bean of type 'cn.itcast.feign.client.UserClient' in your configuration.

This is because UserClient is now under the cn.itcast.feign.clients package,

The @EnableFeignClients annotation of order-service is under the cn.itcast.order package, not in the same package, and the UserClient cannot be scanned.

4) Solve the scan package problem

method one:

Specify the packages Feign should scan:

@EnableFeignClients(basePackages = "cn.itcast.feign.clients")

Method two:

Specify the Client interface to be loaded:

@EnableFeignClients(clients = {UserClient.class})

5) Restart the test again

Tags: Microservices Nacos feign

Posted by xxxxxx on Tue, 01 Nov 2022 17:17:32 +0530