Many developers use Feign as "magic" annotations: you annotate an interface, add @FeignClient, and voilà — requests are sent. But what actually happens behind the scenes? In this article, we will break down the "assembly" process of a Feign client and learn how to intercept the process.
Part 1: The Construction Process
When you initialize a Feign client, a complex transformation occurs, turning a standard Java interface into a working HTTP client. The main stages are:
1. Context Creation
Feign uses a system of contexts (similar to the Spring context) to store configurations. This allows the settings of one client to be isolated from another.
2. Contract Parsing
This is the most critical stage. Feign takes your interface and analyzes its methods and annotations.
- If you are using the standard Contract, it looks for annotations like @RequestLine.
- If you are using Spring Cloud OpenFeign, it uses SpringMvcContract, which understands @GetMapping, @PostMapping, etc.
Result: A tree of MethodMetadata objects is created, describing the URL, parameters, headers, and method type.
3. Proxy Generation
Using java.lang.reflect.Proxy, Feign creates an object that implements your interface. When you call a method on this interface, control is passed to an InvocationHandler.
4. The Invocation Chain
When a method is invoked, the InvocationHandler triggers a chain of components:
- Encoder: Converts method arguments (Java objects) into the request body (JSON, XML, etc.).
- Request Template: Assembles the final HTTP request (URL + Method + Headers + Body).
- Client: Executes the actual network call (via OkHttp, Apache HttpClient, or the standard JDK client).
- Decoder: Converts the response back into a Java object.
- ErrorDecoder: If the response status is not 2xx, control is passed here to handle errors.
Part 2: Customization and Extension
Standard behavior is often insufficient (e.g., you might need to add a specific signature to a header or change the error handling logic).
1. Custom Decoder and Encoder
If you need to use a specific format (for example, Protobuf instead of JSON), you must implement the Decoder interface.
public class MyCustomDecoder implements Decoder { @Override public Object decode(Response response, Type type) throws IOException { // Your parsing logic return customParser.parse(response.body().asInputStream(), type); } }2. Custom ErrorDecoder
This is the best way to turn HTTP errors (404, 400, 500) into meaningful business exceptions for your application.
public class MyErrorDecoder implements ErrorDecoder { @Override public Exception decode(String methodKey, Response response) { if (response.status() == 404) { return new ResourceNotFoundException("Resource not found on the service side"); } return new Default().decode(methodKey, response); } }3. Custom Contract
If you want to use your own custom annotations to manage requests, you will need to implement the Contract interface. This allows you to override how Feign interprets the methods of your interface.