What is a Fegin

Feign is a declarative WebService client. Using Feign makes it easier to write Web Service clients by defining an interface and then adding annotations to it, as well as supporting JAX-RS standard annotations. Feign also supports pluggable encoders and decoders. Spring Cloud encapsulates Feign to support Spring MVC standard annotations and HttpMessageConverters. Feign can be used in combination with Eureka and the Ribbon to support load balancing.

This article focuses on the specific invocation link of Fegin in SpringCloud and the underlying implementation.

Initialization of Feign

@ EnableFeignClients annotations

The @enableFeignClients annotation needs to be added for FeGIN to take effect. Let’s look at EnableFeignClients:

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(FeignClientsRegistrar.class) public @interface EnableFeignClients {// Configurable omitted... }Copy the code

With the familiar annotation @import, look at the FeignClientsRegistrar class

Register beanDefinition FeignClientsRegistrar. Java

Main method entry:

FeignClientsRegistrar#registerBeanDefinitions

@Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {/ / by EnableFeginClients Configuration build Configuration registerDefaultConfiguration (metadata, registry); // registerFeignClient registerFeignClients(metadata, registry); }Copy the code

FeignClientsRegistrar#registerDefaultConfiguration

private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {// Get annotation configuration Map<String, Object> defaultAttrs = metadata .getAnnotationAttributes(EnableFeignClients.class.getName(), true); if (defaultAttrs ! = null && defaultAttrs.containsKey("defaultConfiguration")) { String name; / / whether to obtain outside class the if (metadata) hasEnclosingClass ()) {name = "default." + metadata. GetEnclosingClassName (); } else { name = "default." + metadata.getClassName(); } / / registered the Configuration object registerClientConfiguration (registry, name, defaultAttrs. Get (" defaultConfiguration ")); }}Copy the code

FeignClientsRegistrar#registerFeignClients

How to register FeignClient

Public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {//scanner Access to a scanner ClassPathScanningCandidateComponentProvider scanner = getScanner (); / / set the resource loaders scanner. SetResourceLoader (enclosing resourceLoader); // Set<String> basePackages; / / annotation configuration Map < String, Object > attrs = metadata. GetAnnotationAttributes (EnableFeignClients. Class. GetName ()); AnnotationTypeFilter = new AnnotationTypeFilter(feignClient.class); final Class<? >[] clients = attrs == null ? null : (Class<? >[]) attrs.get("clients"); // No clients attribute configured, filter set, And package path if esau (clients = = null | | clients. The length = = 0) {scanner. AddIncludeFilter (annotationTypeFilter); basePackages = getBasePackages(metadata); } // The clients attribute is configured else {... } // Package path for (String basePackage: BasePackages) {// Scan all BeanDefinition objects that match the filter Set<BeanDefinition> candidateComponents = scanner .findCandidateComponents(basePackage); for (BeanDefinition candidateComponent : CandidateComponents) {if (candidateComponent instanceof AnnotatedBeanDefinition) {// Validate is an interface type AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent; AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface"); // Get @feignClient configuration Map<String, Object> attributes = annotationMetadata .getAnnotationAttributes( FeignClient.class.getCanonicalName()); String name = getClientName(attributes); / / to individual FeignClient configuration register registerClientConfiguration (registry, the name, the attributes. The get (" configuration ")); // Register a FactoryBean registerFeignClient(Registry, annotationMetadata, Attributes); }}}}Copy the code
FeignClientsRegistrar#registerFeignClient

Here are the steps to register the FeignClientFactoryBean into the IOC container. You can see below. The FeignClient configuration Settings are resolved to BeanDefinitionBuilder, and AbstractBeanDefinition is generated. Finally, BeanDefinitionHolder is generated for BeanDefinition registration. BeanDefinitionHolder is a hook class that records information such as the BeanDefinition corresponding to the class name

private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    String className = annotationMetadata.getClassName();
    BeanDefinitionBuilder definition = BeanDefinitionBuilder
                    .genericBeanDefinition(FeignClientFactoryBean.class);
    validate(attributes);
    definition.addPropertyValue("url", getUrl(attributes));
    definition.addPropertyValue("path", getPath(attributes));
    String name = getName(attributes);
    definition.addPropertyValue("name", name);
    definition.addPropertyValue("type", className);
    definition.addPropertyValue("decode404", attributes.get("decode404"));
    definition.addPropertyValue("fallback", attributes.get("fallback"));
    definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
    definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

    String alias = name + "FeignClient";
    AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

    boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null

    beanDefinition.setPrimary(primary);

    String qualifier = getQualifier(attributes);
    if (StringUtils.hasText(qualifier)) {
            alias = qualifier;
    }

    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
                    new String[] { alias });
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
Copy the code

At this point the EnableFeignClients annotation is done, which is simply to scan all packages, register all @FeignClient classes as FactoryBeans, and then use the getObject method to get the corresponding class when the bean is created.

FeignClientFactoryBean. Java beans generated objects

Next look at the getObject method of the FeignClientFactoryBean, which calls the getTarget method

FeignClientFactoryBean#getObject

@Override
public Object getObject() throws Exception {
    return getTarget();
}
Copy the code

FeignClientFactoryBean#getTarget

Go to the getTarget method and look only at the case where the URL is not configured

< T > T getTarget () {/ / access to feign context FeignContext context. = applicationContext getBean (FeignContext. Class); Feign.Builder = Feign (context); Feign (context); // If FeignClient is not configured with the specified URL, go this way. StringUtils.hasText(this.url)) { String url; // If there is no configuration and name does not start with HTTP, spell protocol if (! this.name.startsWith("http")) { url = "http://" + this.name; } else { url = this.name; } url += cleanPath(); Return (T) loadBalance(Builder, context, new HardCodedTarget<>(this.type, this.name, url)); return (T) loadBalance(Builder, context, new HardCodedTarget<>(this.type, this.name, url)); }... }Copy the code

FeignClientFactoryBean#loadBalance

Now what does the loadBalance method do

protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {// This is a LoadBalanceFeignClient object. Client = getOptional(context, client.class); if (client ! = null) {// Wrap client Builder.client (client); Targeter targeter = get(context, Targeter.class); Return targeter.target(this, builder, context, target); }}Copy the code

HystrixTargeter#target

See the target method, primarily the feign.target method

public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
						Target.HardCodedTarget<T> target) {
    if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
        return feign.target(target);
    }	
    ...
}
Copy the code

Feign.target can see that the object is instantiated, so focus on what build() does

public <T> T target(Target<T> target) {
  return build().newInstance(target);
}
Copy the code

Feign. Build mainly initialization SynchronousMethodHandler. Factory and generates ReflectiveFeign object, into the ReflectiveFeign newInstance method.

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);
  return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
Copy the code

ReflectiveFeign#newInstance

Public <T> T newInstance(Target<T> Target) { MethodHandler> nameToHandler = targetToHandlersByName.apply(target); Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>(); List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>(); For (Method Method: target.type().getmethods ()) {... } // Generate interceptors are familiar with JDK dynamic proxy requirements! InvocationHandler handler = factory.create(target, methodToHandler); T proxy = (T) proxy.newproxyInstance (target.type().getClassLoader(), new Class<? >[]{target.type()}, handler); for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) { defaultMethodHandler.bindTo(proxy); } return proxy; }Copy the code

As shown below, nameToHandler is storedAfter looking at the generated invocationHandler, you only need to worry about what happens in the invocationHandler.

Fegin calls

ReflectiveFeign# FeignInvocationHandler# invoke method

The Dispatches here are actually the methodToHandler that was just passed in, and that methodToHandler logs the Method and its corresponding handler class, the SynchronousMethodHandler

 @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  ...
  return dispatch.get(method).invoke(args);
}
Copy the code

So go to the Invoke method of the SynchronousMethodHandler

SynchronousMethodHandler#invoke

So you can see that we’re doing a retry here with a while, and then we see a try catch, so normal business code is written in a try, so we go straight to executeAndDecode

public Object invoke(Object[] argv) throws Throwable { RequestTemplate template = buildTemplateFromArgs.create(argv); Retryer retryer = this.retryer.clone(); while (true) { try { return executeAndDecode(template); } catch (RetryableException e) { retryer.continueOrPropagate(e); if (logLevel ! = Logger.Level.NONE) { logger.logRetry(metadata.configKey(), logLevel); } continue; }}}Copy the code

SynchronousMethodHandler#executeAndDecode

Let’s go straight to the execute method in the main try, and the client here is LoadBalanceFeignClient, so LoadBalanceFeignClient

Object executeAndDecode(RequestTemplate template) throws Throwable { Request request = targetRequest(template); . Response response; long start = System.nanoTime(); try { response = client.execute(request, options); // ensure the request is set. TODO: remove in Feign 10 response.toBuilder().request(request).build(); } catch (IOException e) { ... }... }Copy the code

LoadBalancerFeignClient#execute

Here you can see the familiar lbClient and ribbon work on load balancing

@Override public Response execute(Request request, Request.Options options) throws IOException { try { URI asUri = URI.create(request.url()); String clientName = asUri.getHost(); URI uriWithoutHost = cleanUrl(request.url(), clientName); FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest( this.delegate, request, uriWithoutHost); IClientConfig requestConfig = getClientConfig(options, clientName); return lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse(); } catch (ClientException e) { ... }}Copy the code

The next see AbstractLoadBalancerAwareClient executeWithLoadBalancer method

AbstractLoadBalancerAwareClient#executeWithLoadBalancer

The Submit method of LoadBalancerCommand passes in a ServerOperation object and goes directly to the Submit method

 public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
    LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);

    try {
        return command.submit(
            new ServerOperation<T>() {
                @Override
                public Observable<T> call(Server server) {
                    URI finalUri = reconstructURIWithServer(server, request.getUri());
                    S requestForServer = (S) request.replaceUri(finalUri);
                    try {
                        return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                    } 
                    catch (Exception e) {
                        return Observable.error(e);
                    }
                }
            })
            .toBlocking()
            .single();
    } catch (Exception e) {
       ...
    }
Copy the code

LoadBalancerCommand#submit

We just need to focus on the key steps

final ExecutionInfoContext context = new ExecutionInfoContext(); . // Use the load balancer Observable<T> o = (server == null ? selectServer() : Observable.just(server)) ... return operation.call(server).doOnEach(new Observer<T>() { private T entity; @Override public void onCompleted() { recordStats(tracer, stats, entity, null); // TODO: What to do if onNext or onError are never called? } @Override public void onError(Throwable e) { recordStats(tracer, stats, null, e); logger.debug("Got error {} when executed on server {}", e, server); if (listenerInvoker ! = null) { listenerInvoker.onExceptionWithServer(e, context.toExecutionInfo()); } } @Override public void onNext(T entity) { this.entity = entity; if (listenerInvoker ! = null) { listenerInvoker.onExecutionSuccess(entity, context.toExecutionInfo()); } } private void recordStats(Stopwatch tracer, ServerStats stats, Object entity, Throwable exception) { tracer.stop(); loadBalancerContext.noteRequestCompletion(stats, entity, exception, tracer.getDuration(TimeUnit.MILLISECONDS), retryHandler); }}); }}); . return ... }Copy the code

SelectServer () from selectServer(

server == null ? selectServer() : Observable.just(server)
Copy the code

LoadBalancerCommand#selectServer

Directly obtain getServerFromLoadBalancer server

private Observable<Server> selectServer() { return Observable.create(new OnSubscribe<Server>() { @Override public void call(Subscriber<? super Server> next) { try { Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey); next.onNext(server); next.onCompleted(); } catch (Exception e) { next.onError(e); }}}); }Copy the code

LoadBalancerContext#getServerFromLoadBalancer

Here you see the familiar getLoadBalancer method and chooseServer method

public Server getServerFromLoadBalancer(@Nullable URI original, @nullable Object loadBalancerKey) throws ClientException {// Obtain the load balancer ILoadBalancer lb = getLoadBalancer(); If (host == null) {// If the load balancer is not empty, select server if (lb! = null){ Server svc = lb.chooseServer(loadBalancerKey); . return new Server(host, port); }Copy the code

GetLoadBalancer and chooseServer are not parsed here because the Ribbon has already parsed them.

Once you get the Server, the operation.call method is executed, which is the call method of the ServerOperation object you just passed in. This is a request for service.

 try {
    return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
} 
Copy the code

Java, which has a default implementation of Deafult

public static class Default implements Client

You can see that HttpUrlConnection actually sent the request

@Override
public Response execute(Request request, Options options) throws IOException {
  HttpURLConnection connection = convertAndSend(request, options);
  return convertResponse(connection).toBuilder().request(request).build();
}
Copy the code

This is the Feign initialization and call process! If there is any problem, please big guys point out!

Ribbon portal

What is Ribbon now? Juejin. Cn/post / 696426…