feign introduction
Feign is a Restful client component of Java. Feign makes it easier to write Java HTTP client. Feign was inspired by Retrofit, JAXRS-2.0 and WebSocket. Feign has nearly 3K stars on github, which is a quite excellent open source component. Although it is inferior to nearly 30K stars of Retrofit, spring cloud integrates feign, which makes feign more widely used in Java ecosystem than Retrofit.
The basic principle of feign is to annotate the interface method, define the rest request, construct the dynamic proxy object of the interface, and then send the http request by calling the interface method, and automatically parse the http response as the return value of the method, which greatly simplifies the code for the client to call the rest api. An example of the official website is as follows:
interface GitHub { @RequestLine("GET /repos/{owner}/{repo}/contributors") List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo); } static class Contributor { String login; int contributions; } public static void main(String... args) { GitHub github = Feign.builder() .decoder(new GsonDecoder()) .target(GitHub.class, "https://api.github.com"); // Fetch and print a list of the contributors to this library. List<Contributor> contributors = github.contributors("OpenFeign", "feign"); for (Contributor contributor : contributors) { System.out.println(contributor.login + " (" + contributor.contributions + ")"); } }
Please refer to the official website for feign tutorial https://github.com/OpenFeign/feign/
This paper mainly analyzes the source code of feign, and understands the design architecture and internal implementation technology of feign according to the source code.
Feign.build builds dynamic agent of interface
First, let's see how the dynamic proxy of the interface is built. The following figure is the class diagram of the main interface and class:
From the example above, we can see that the dynamic proxy object of the built interface is constructed by Feign.builder() to generate the constructor object of feign. Builder, then set the relevant parameters, and then call the target method. Feign.Builder's parameters include:
//Interceptor, after assembling the RequestTemplate, intercepts and processes the RequestTemplate before sending the request private final List<RequestInterceptor> requestInterceptors = new ArrayList<RequestInterceptor>(); //log level private Logger.Level logLevel = Logger.Level.NONE; //Contract model, default is Contract.Default, and the user creates MethodMetadata, which is extended by spring cloud to implement spring MVC annotation private Contract contract = new Contract.Default(); //Client, default to Client.Default, can extend Apache httpclient, OKHttpClient, RibbonClient, etc private Client client = new Client.Default(null, null); //Retry setting, not set by default private Retryer retryer = new Retryer.Default(); //Log, can access Slf4j private Logger logger = new NoOpLogger(); //Encoder, for body coding private Encoder encoder = new Encoder.Default(); //Decoder, decoding of user response private Decoder decoder = new Decoder.Default(); //Parameter encoder annotated with @ QueryMap private QueryMapEncoder queryMapEncoder = new QueryMapEncoder.Default(); //Request error decoder private ErrorDecoder errorDecoder = new ErrorDecoder.Default(); //Parameter configuration, mainly including timeout private Options options = new Options(); //Dynamic agent factory private InvocationHandlerFactory invocationHandlerFactory = new InvocationHandlerFactory.Default(); //decode404 or not private boolean decode404; private boolean closeAfterDecode = true; // Exception isolation strategy private ExceptionPropagationPolicy propagationPolicy = NONE;
This is a typical constructor pattern. In the target method, first call the build method to create a new ReflectFeign object, and then call the newInstance method of ReflectFeign to create a dynamic agent. The code is as follows:
//HardCodedTarget is used by default public <T> T target(Class<T> apiType, String url) { return target(new HardCodedTarget<T>(apiType, url)); } public <T> T target(Target<T> target) { return build().newInstance(target); } public Feign build() { SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory( client, retryer, requestInterceptors, logger, logLevel, decode404, closeAfterDecode); ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, errorDecoder, synchronousMethodHandlerFactory); // handlersByName encapsulates all parameters and provides logic for parsing interface methods // invocationHandlerFactory is a Builder property. The default value is InvocationHandlerFactory.Default // Implementation of InvocationHandler for creating java Dynamic Proxy return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder); }
Target source code is as follows:
public interface Target<T> { // api interface class Class<T> type(); // name String name(); // The prefix address of the api interface path (for example, http://localhost:8080), which will be used when sending the request and the url path in @ RequestLine (/ user/list) // Spliced together as a request url(http://localhost:8080/user/list) String url(); public Request apply(RequestTemplate input); /** * Target Default implementation of, do nothing */ public static class HardCodedTarget<T> implements Target<T> { private final Class<T> type; private final String name; private final String url; public HardCodedTarget(Class<T> type, String url) { // You can see that the name is the same as the url by default this(type, url, url); } public HardCodedTarget(Class<T> type, String name, String url) { this.type = checkNotNull(type, "type"); this.name = checkNotNull(emptyToNull(name), "name"); this.url = checkNotNull(emptyToNull(url), "url"); } @Override public Request apply(RequestTemplate input) { if (input.url().indexOf("http") != 0) { input.target(url()); } return input.request(); } } }
The ReflectiveFeign constructor has three parameters:
- ParseHandlersByName encapsulates all the builder parameters and provides the logic to parse interface methods
- InvocationHandlerFactory java the factory class of the InvocationHandler of the dynamic agent. The default value is InvocationHandlerFactory.Default
- When the QueryMap Encoder Interface Parameter annotation @ QueryMap, the encoder of the parameter
The ReflectiveFeign.newInstance method creates an interface dynamic proxy object:
public <T> T newInstance(Target<T> target) { //targetToHandlersByName is the ParseHandlersByName object passed in by the constructor. The MethodHandler mapping is generated based on the target object Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target); Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>(); List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>(); //Traverse all methods of the interface and build the mapping of method - > methodhandler for (Method method : target.type().getMethods()) { if (method.getDeclaringClass() == Object.class) { continue; } else if(Util.isDefault(method)) { //Handler of interface default method, which directly calls DefaultMethodHandler handler = new DefaultMethodHandler(method); defaultMethodHandlers.add(handler); methodToHandler.put(method, handler); } else { methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method))); } } //Here, factory constructs the incoming, and creates the InvocationHandler InvocationHandler handler = factory.create(target, methodToHandler); //Dynamic agent of java T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler); //Bind the default method directly to the dynamic agent for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) { defaultMethodHandler.bindTo(proxy); } return proxy; }
Most of the content comes from: http://techblog.ppdai.com/2018/05/14/20180514/
I add my understanding to it