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:
type | effect | illustrate |
---|---|---|
feign.Logger.Level | Change log level | Contains four different levels: NONE (default), BASIC, HEADERS, FULL |
feign.codec.Decoder | The parser of the response result | Parse the result of http remote call, such as parsing json string into java object |
feign.codec.Encoder | request parameter encoding | Encode request parameters for easy sending via http requests |
feign. Contract | Supported annotation formats | The default is SpringMVC annotations |
feign. Retryer | failure retry mechanism | The 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:
- Use a connection pool instead of the default URLConnection
- 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