Spring Cloud OpenFeign is a declarative service call and load balancing component officially launched by Spring. Its bottom layer is based on Netflix Feign, which is an open source declarative WebService client designed by Netflix to simplify communication between services.
Spring Cloud openfeign enhances Feign to support Spring MVC annotations, and integrates Ribbon and Nacos, making Feign more convenient to use.
https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/
Principle
Dynamic Proxy is used
Compare Dubbo
It seems that OpenFeign cannot directly call the service layer. The general way of writing is that the service provider must write the controller interface, but dubbo does not need to write the controller interface. If the service layer cannot be called directly and the controller layer must be written, then this should be a disadvantage, because an additional controller method needs to be written every time.
Moreover, on the service consumer side, an additional remote call service interface with @FeignClient
annotation should be written. It seems that Spring Cloud’s OpenFeign
is much more troublesome than dubbo
.
Introducing dependencies
Using OpenFeign components requires the introduction of client dependencies
<!--OpenFeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
Use case
Add the @EnableFeignClients
annotation to the startup class
/** * callee */ @RestController public class WarehouseController {<!-- --> /** * Query the inventory status of the corresponding skuId * @param skuId skuId * @return Stock stock object */ @GetMapping("/stock") public Stock getStock(Long skuId){<!-- --> // ...omitted } } /** * caller */ @FeignClient("warehouse-service") public interface WarehouseServiceFeignClient {<!-- --> @GetMapping("/stock") public Stock getStock(@RequestParam("skuId") Long skuId); }
The @FeignClient annotation indicates that the current interface is an OpenFeign communication client, and the parameter value warehouse-service is the service provider ID (note that the OpenFeign service name does not support the underscore _, which is a pitfall). This item must be consistent with the Nacos registration ID.
Before OpenFeign sends a request, it will automatically query all available instance information of warehouse-service in Nacos, and then select an instance to initiate a RESTful request through the built-in Ribbon load balancing, thereby ensuring high availability of communication.
tips: Although the return values here are all written in the same type, they are not necessarily the same class. Essentially, they interact with Json, as long as the attributes are correct.
Detailed explanation of @FeignClient annotation attributes
contextId: If contextId is configured, this value will be used as beanName.
fallback: Defines a fault-tolerant processing class. When calling a remote interface fails or times out, the fault-tolerant logic of the corresponding interface will be invoked. The class specified by fallback must implement the interface marked by @FeignClient
fallbackFactory: Factory class, used to generate fallback class examples, through this property we can implement common fault-tolerant logic for each interface and reduce repetitive code
url: url is generally used for debugging, you can manually specify the address called by @FeignClient
Spring Cloud Circuit Breaker Fallbacks
The default logic executed when the circuit breaker is opened or an error occurs can be configured by fallback
Note: You also need to declare it as a Spring bean
.
@FeignClient(name = "test", url = "http://localhost:${server.port}/", fallback = Fallback.class) protected interface TestClient {<!-- --> @RequestMapping(method = RequestMethod. GET, value = "/hello") Hello getHello(); @RequestMapping(method = RequestMethod. GET, value = "/hellonotfound") String getException(); } @Component static class Fallback implements TestClient {<!-- --> @Override public Hello getHello() {<!-- --> throw new NoFallbackAvailableException("Boom!", new RuntimeException()); } @Override public String getException() {<!-- --> return "Fixed response"; } }
If you need to access what caused the fallback to fire, you can use the fallbackFactory
attribute
@FeignClient(name = "testClientWithFactory", url = "http://localhost:${server.port}/", fallbackFactory = TestFallbackFactory.class) protected interface TestClientWithFactory {<!-- --> @RequestMapping(method = RequestMethod. GET, value = "/hello") Hello getHello(); @RequestMapping(method = RequestMethod. GET, value = "/hellonotfound") String getException(); } @Component static class TestFallbackFactory implements FallbackFactory<FallbackWithFactory> {<!-- --> @Override public FallbackWithFactory create(Throwable cause) {<!-- --> return new FallbackWithFactory(); } } static class FallbackWithFactory implements TestClientWithFactory {<!-- --> @Override public Hello getHello() {<!-- --> throw new NoFallbackAvailableException("Boom!", new RuntimeException()); } @Override public String getException() {<!-- --> return "Fixed response"; } }
Enable logging
In our work, we have some production problems in the mutual calls of microservices OpenFeign, but the calls of OpenFeign do not log by default.
We can configure by code or configuration file
//Global configuration @Configuration public class FeignLoggerConfiguration {<!-- --> @Bean Logger.Level feignLoggerLevel() {<!-- --> return Logger.Level.FULL; } } //local configuration //No need to add Configuration annotation public class FeignConfig {<!-- --> @Bean public Logger.Level feignLogLevel(){<!-- --> //Set the log printing level of feign client to FULL return Logger.Level.FULL; } } //configuration attribute, the value is Class, and the configuration information takes effect for the service named userprivilege @Resource @FeignClient(name="userprivilege", fallback = FallBack_PrivilegeService.class , configuration = FeignConfig.class) public interface PrivilegeService {<!-- --> }
feign: client: config: default: # project global loggerLevel: HEADERS order-service: The service name configured in the #@FeignClient annotation loggerLevel: FULL
Level has four levels
- NONE Do not log, the default value
- BASIC only records method, url, response code, execution time
- HEADERS Only log request and response headers
- FULL all logged
The log level of openfeign is modified above to be debug, but the default log level of springboot is info, because debug https://www.baeldung.com/java-feign-file-upload OpenFeign uses Java’s built-in URLConnection object to create HTTP requests by default, but when connecting to production, if the underlying communication components can be replaced with dedicated communication components such as Apache HttpClient and OKHttp, based on the connection pools that come with these components, you can better Reuse and manage HTTP connection objects. Then add the following to the configuration file: After the above settings, you can use okhttp, because automatic assembly has been realized in In OpenFeign, data compression is not enabled by default. But if you transfer more than 1K bytes of data between services, it is strongly recommended to enable data compression. By default, OpenFeign uses Gzip to compress data. For large text, the compressed size is usually only 10%~30% of the original data, which will greatly improve bandwidth utilization. , add the following configuration in the project configuration file application.yml: Tip Reminder: If the application is computationally intensive and the CPU load exceeds 70% for a long time, since data compression and decompression require CPU operations, turning on the data compression function will add an additional burden to the CPU instead, resulting in system performance lower, which is not advisable. In this case It is recommended not to enable data compression OpenFeign uses Ribbon by default to implement client load balancing, and its default load balancing strategy is the round robin strategy. So how to set Ribbon’s default load balancing strategy? Just adjust the load balancing class used when microservices communicate in application.yml. Tip Reminder: For performance considerations, we can choose to use weight strategy or region-sensitive strategy instead of polling strategy, because such execution efficiency is the highest. Before understanding this practice, we need to know Feign’s writing support for interface inheritance Reference: https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-cloud-feign-inheritance Feign supports template APIs through single-inheritance interfaces. This allows grouping of common operations into convenient base interfaces. Feign supports inheritance and multiple inheritance, so that we can expose the API to the interface, the service provider implements the interface to provide services, and the service consumer only needs to import the API module where the interface is located. But earlier, the government did not advocate doing so. Related thinking can be seen: tips: @FeignClient interface should not be shared between server and client, and annotating @FeignClient interface with @RequestMapping at class level is not supported. Extract Feign’s Client as an independent module, and put the interface-related POJO and default Feign configuration into this module for all consumers to use. For example: Extract the default configurations of UserClients, User, and Feign into a feign-api package, and all microservices can refer to this dependency package and use it directly. The RPC interface and implementation are placed in separate modules to facilitate service callers to reuse the service interface. The service interface module can only contain the most basic module dependencies (too many will lead to dependency transfer). The service interface and related models must be placed in the same module, which only contains these contents. The FeignClient annotation cannot be specified in the interface of the service provider (specified on the consumer side), and annotations such as @RequestMapping can be used in the method< /strong>. For example: The account-api module puts the things that consumers need to use, api interface, vo, input parameters, etc… account-service implements the interface provided by account-api Refer to the RPC interface module, write the feign client class on the consumer side to inherit the relevant interface and handle the fuse, and add the FeignClient annotation to the client interface. Since Spring now provides its own HTTP interface client solution, for example, there are two solutions for implementing interface calls in the latest Spring Boot 3.0: Therefore, starting from the Spring Cloud 2022.0.0 version, the Spring Cloud OpenFeign module has been considered functionally complete, which means that the Spring Cloud team will no longer add new features to the module. Although OpenFeign will not add new features, it will continue to fix bugs and security issues, and will also consider and review small pull requests from the community. Spring 6 brings a new feature – HTTP Interface. This new feature allows developers to define an HTTP service as a Java interface containing a method marked with a specific annotation, and then complete the HTTP request by calling the interface method. It looks a lot like using https://docs.spring.io/spring-framework/docs/6.0.0-RC1/reference/html/integration.html#rest-http-interfacelogging:
level:
com.base.service: debug # This is the package path where the openfeign client is located
Upload files
@PostMapping(value = "/upload-file")
public String handleFileUpload(@RequestPart(value = "file") MultipartFile file) {<!-- -->
// File upload logic
}
public class FeignSupportConfig {<!-- -->
@Bean
public Encoder multipartFormEncoder() {<!-- -->
return new SpringFormEncoder(new SpringEncoder(new ObjectFactory<HttpMessageConverters>() {<!-- -->
@Override
public HttpMessageConverters getObject() throws BeansException {<!-- -->
return new HttpMessageConverters(new RestTemplate().getMessageConverters());
}
}));
}
}
@FeignClient(name = "file", url = "http://localhost:8081", configuration = FeignSupportConfig.class)
public interface UploadClient {<!-- -->
@PostMapping(value = "/upload-file", consumes = MediaType. MULTIPART_FORM_DATA_VALUE)
String fileUpload(@RequestPart(value = "file") MultipartFile file);
}
Performance optimization
Replace the default communication component
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
<!-- or add httpclient framework dependency -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
feign:
okhttp:
enabled: true
# or
Feign:
httpclient:
enabled: true
FeignAutoConfiguration
Data compression
feign:
compression:
request:
enabled: true # Turn on the compression function of the request data
mime-types: text/xml, application/xml, application/json # compression type
min-request-size: 1024 # The minimum compression value standard, when the data is greater than 1024, it will be compressed
response:
enabled: true # Turn on the response data compression function
Load balancing
warehouse-service: #The microservice ID of the service provider
ribbon:
#Set the corresponding load balancing class
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
A practice of encapsulating OpenFegin
Feign inheritance support
public interface UserService {<!-- -->
@RequestMapping(method = RequestMethod. GET, value = "/users/{id}")
User getUser(@PathVariable("id") long id);
}
@RestController
public class UserResource implements UserService {<!-- -->
@Override
public User getUsers() {<!-- -->
...
}
}
@FeignClient("users")
public interface UserClient extends UserService {<!-- -->
}
Practice
Service provider development practices
account
-account-api
-account-service
public interface AccountApi {<!-- -->
@RequestMapping(method = RequestMethod. GET, value = "/users/{id}")
User getUser(@PathVariable("id") long id);
}
@RestController
@Api(tags = "User Interface")
public class AccountController implements AccountApi {<!-- -->
...
}
Service consumer development practice
@Component
@FeignClient(name = "account-service", fallbackFactory = AccountClientFallbackFactory.class)
public interface AccountClient extends AccountApi {<!-- -->
...
}
@Component
public class AccountClientFallbackFactory implements FallbackFactory<AccountClient> {<!-- -->
@Override
public AccountClient create(Throwable throwable) {<!-- -->
AccountClientFallback accountClientFallback = new AccountClientFallback();
accountClientFallback.setCause(throwable);
return accountClientFallback;
}
}
@Slf4j
public class AccountClientFallback implements AccountClient {<!-- -->
@Setter
private Throwable cause;
@Override
public ResultData<AccountDTO> getByCode(String accountCode) {<!-- -->
...
}
}
Extension
Spring Cloud OpenFeign Feature Completion Announcement
HTTP Interface
Feign
to complete remote service calls.