Use of feign in spring cloud and source code analysis

Use of feign in spring cloud and source code analysis

The role of feign

Feign can achieve the same experience as calling local methods when using HTTP to request remote services. Developers are completely unaware that this is the remote party
Method, and I don't realize that this is an HTTP request. Like Dubbo, the consumer directly calls the interface method to call the provider instead of
It is necessary to construct the request through the conventional Http Client and then parse the returned data. It solves the problem that allowing developers to call remote interfaces is like calling local methods
Similarly, there is no need to pay attention to the details of remote interaction, let alone the development of distributed environment.

Use of feign

Annotate @ EnableFeignClients on the main method

@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients
public class OrderMain83 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain83.class,args);
    }
}

Define your own service

@Component
@FeignClient(value = "cloudalibaba-nacos-provider")
public interface PaymentService {

    @GetMapping(value = "/payment/get/{id}")
      CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
}

Define your own controller

@RestController
public class FeignController  {

    @Autowired
    private PaymentService paymentService;

    @GetMapping(value = "nacos/consumer/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
        return paymentService.getPaymentById(id);
    }
}

Through these two steps, you can call the remote service like calling the local interface

feign source code analysis

Overall code flow chart

Parsing of EnableFeignClients annotation

Start with the @ EnableFeignClients annotation

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

A feignclientsregister was import ed

class FeignClientsRegistrar
		implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

Feignclientsregister inherits importbeandefinitionregister, so let's look at the registerBeanDefinitions method of feignclientsregister to see what BeanDefinition is injected

Feignclientsregister#registerfeignclients method

First, get the attribute of the annotation EnableFeignClients

Map<String, Object> attrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName());

new provides a filter for annotations

AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
				FeignClient.class);

Then you get the package that needs to be scanned

basePackages = getBasePackages(metadata);

Then there is a for loop, which traverses the basePackages, and then finds the Components annotated with FeignClient according to the package

Set<BeanDefinition> candidateComponents = scanner
					.findCandidateComponents(basePackage);

Then it's time to register the BeanDefinition method, registerfeignclient (registry, annotation metadata, attributes);

	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);
		String contextId = getContextId(attributes);
		definition.addPropertyValue("contextId", contextId);
		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 = contextId + "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);
	}

A long list of definition setting attributes, including url, path, fallback, etc.
The key is

BeanDefinitionBuilder definition = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientFactoryBean.class);

The type of the bean was changed to FeignClientFactoryBean.

Initialization of Bean

Let's take a look at FeignClientFactoryBean first

class FeignClientFactoryBean
		implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {

FeignClientFactoryBean implements FactoryBean. It shows that the bean is user-defined instantiated. Look directly at the getObject method

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

The getObject method calls the getTarget method and directly returns the value. The return method is

return (T) loadBalance(builder, context,
					new HardCodedTarget<>(this.type, this.name, this.url));
	}

Look at the loadBalance method,

	protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
			HardCodedTarget<T> target) {
		//Put the LoadBalancerFeignClient in
		Client client = getOptional(context, Client.class);
		if (client != null) {
			builder.client(client);
			Targeter targeter = get(context, Targeter.class);
			return targeter.target(this, builder, context, target);
		}

		throw new IllegalStateException(
				"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
	}

Get the target according to the context and execute the target. Target (this, builder, context, target);

Since hytrix is not introduced, only feign is introduced, so feign.target(target) is called directly

if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
			return feign.target(target);
		}

Enter feign.target

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

Advanced build method

public Feign build() {
      SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
          new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
              logLevel, decode404, closeAfterDecode, propagationPolicy);
      ParseHandlersByName handlersByName =
          new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
              errorDecoder, synchronousMethodHandlerFactory);
      return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
    }
  }

Construct some properties, return new, return reflective feign, and then enter the newInstance method

  public <T> T newInstance(Target<T> target) {
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
	//Get all the methods of the class
    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if (Util.isDefault(method)) {
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
      //Put method into methodToHandler
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    //Get InvocationHandler 
    InvocationHandler handler = factory.create(target, methodToHandler);
    //Use dynamic proxy to get proxy class
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }

First, all the methods in this class are parsed
Put it in methodToHandler
Get InvocationHandler (FeignInvocationHandler)
Generate dynamic proxy

Generated methodToHandler

Generated dynamic proxy class

In this way, the bean is created.

Method call

Those who have known about JDK dynamic proxy before will know that when calling the method of the proxy class, they will first enter the invoke method of InvocationHandler, that is, the invoke method of FeignInvocationHandler

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      if ("equals".equals(method.getName())) {
        try {
          Object otherHandler =
              args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
          return equals(otherHandler);
        } catch (IllegalArgumentException e) {
          return false;
        }
      } else if ("hashCode".equals(method.getName())) {
        return hashCode();
      } else if ("toString".equals(method.getName())) {
        return toString();
      }

      return dispatch.get(method).invoke(args);
    }

Then dispatch.get(method).invoke(args) will be called
dispatch is the SynchronousMethodHandler put in when generating reflective feign, which will be called next
Executeanddeode of SynchronousMethodHandler

 Request request = targetRequest(template);
 response = client.execute(request, options);

Generate the request and call the execute method of the client. The client is the LoadBalancerFeignClient placed in the loadBalance of FeignClientFactoryBean

Client client = getOptional(context, Client.class);

Then go to the execute method of LoadBalancerFeignClient

Then integrate ribbon to realize load balancing, assemble request and call remote methods.

Tags: Spring Cloud feign

Posted on Fri, 17 Sep 2021 22:59:52 -0400 by shamoon