Abstract

Hello, everyone! I’m Wang Lufei, a novice editor! Some time ago because of work needs, xiaobian to spring-cloud-starter- OpenFeign component source code for in-depth learning, is the so-called alone lele than all lele (in fact, is to review *_*), xiaobian will occasionally share with you their own source code analysis results, I hope to help you!

The premise

I will talk about feign’s implementation logic in a very general way. That is, at the start of the project, we use Java JDK proxy technology to generate proxy objects. When we call their client interface, we actually call proxy objects. Finally, use the client object to make HTTP requests! (General, or I don’t have to say later *><*)

Add a wave of Maven dependencies to OpenFeign.

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
Copy the code

Start the registration

Let’s start with the @enableFeignClients startup annotation. This annotation defines the feIGN client valid scan scope and global configuration information

  • Value and basePackages: scan modes. Feign client scan ranges. All clients in the range are started and are mutually exclusive with the specified mode
  • DefaultConfiguration: Global configuration class, mainly Encoder, Decoder, Retryer, Contract, etc
  • Clients: Specifies the startup client. This mode is mutually exclusive with the scan mode

Explains the attribute, we enter the @ EnableFeignClients, we see a @ Import (FeignClientsRegistrar. Class) annotations, @ Import notes the role of the main can Import FeignClientsRegistrar configuration class, FeignClientsRegistrar class – this class implements the ImportBeanDefinitionRegistrar interface, this interface provides the function of registration of the bean (interested can go to understand, mybatis in the similar way to achieve mapper registration). The main purpose here is to register the Feign client into the Spring container

Registering clients

Let’s go to the FeignClientsRegistrar class and just inherit the registerBeanDefinitions() method

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
	registerDefaultConfiguration(metadata, registry);
	registerFeignClients(metadata, registry);
}
Copy the code

RegisterDefaultConfiguration: mainly used in registered within the scope of feign global configuration file

private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {// Read attributes Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true); If (defaultAttrs! = null && defaultAttrs.containsKey("defaultConfiguration")) { String name; if (metadata.hasEnclosingClass()) { name = "default." + metadata.getEnclosingClassName(); } else { name = "default." + metadata.getClassName(); } / / registered configuration class registerClientConfiguration (registry, name, defaultAttrs. Get (" defaultConfiguration ")); }}Copy the code

There are two things to say about configuration information classes: 1. The global configuration class uses default.* When the configuration name is used, the client independent configuration class uses the service name. 2. All configuration classes are registered with the FeignClientSpecification type, which includes multiple configuration information classes and configuration names, and is referenced by the FeignContext object.

RegisterFeignClients: Mainly used to register fIGN client factory classes

Public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { Scan all classes annotated with FeignCLient //.... // Check whether attribute clients is not empty, if there is a client, do not scan //... // Start the scan logic for (String basePackage: BasePackages) {/ / scan package Set < BeanDefinition > candidateComponents = scanner. FindCandidateComponents (basePackage); for (BeanDefinition candidateComponent : candidateComponents) { if (candidateComponent instanceof AnnotatedBeanDefinition) { AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent; AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); / / check feignClient annotation configuration is interface Assert. IsTrue (annotationMetadata. IsInterface (), "@FeignClient can only be specified on an interface"); // Parse feignClient configuration Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName()); String name = getClientName(Attributes); / / registered client configuration registerClientConfiguration (registry, name, attributes, the get (" configuration ")); // registerFeignClient registerFeignClient(registry, annotationMetadata, attributes); }}}}Copy the code

The main properties of @feignClient

  • Name, Value, and serviceId: indicates the service name. ServiceId is a deprecated attribute
  • Url: request address, which has a higher priority
  • Configuration: Indicates the independent configuration information of the client
  • Fallback and fallbackFactory: Hystrix fuse handling classes (I may share the Hystrix process independently later)
  • Path: indicates a unified address prefix

Now let’s focus on how to register the client

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { String className = annotationMetadata.getClassName(); // Create BeanDefinitionBuilder definition = for feign client generated classes BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class); validate(attributes); / / @ FeignClient attribute extraction / / url, path, can realize configuration name three injection, interested in can be further 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"; / / primary attribute AbstractBeanDefinition beanDefinition = definition. GetBeanDefinition (); boolean primary = (Boolean)attributes.get("primary"); beanDefinition.setPrimary(primary); // Attribute String qualifier = getQualifier(attributes); if (StringUtils.hasText(qualifier)) { alias = qualifier; } BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias }); / / registered BeanDefinitionReaderUtils. RegisterBeanDefinition (holder, registry); }Copy the code

The object generated by the client is actually the FeignClientFactoryBean class, which implements the FactoryBean interface and uses the factory mode to achieve the function of generating different bean objects.

I’ve covered the whole process of registering a Feign client; When Spring initializes the bean, the FeignClientFactoryBean object will generate the concrete proxy object (see JDK Proxy for how to invoke it).

Generating proxy objects

We see FeignClientFactoryBean directly. GetObect (), this method is used to generate a proxy object entry method

Public Object getObject() throws Exception {// Obtain the Feign container FeignContext Context = applicationContext.getBean(FeignContext.class); // Get the constructor from the feign container. If hystrix is enabled, hystrixfeign.builder is returned. Feign.Builder Builder = Feign (context); Feign (context); // Check if there is a URL, if the ribbon handles if (! StringUtils.hasText(this.url)) { String url; if (! this.name.startsWith("http")) { url = "http://" + this.name; } else { url = this.name; } url += cleanPath(); Return loadBalance(Builder, Context, new HardCodedTarget<>(this.type, this.name, url)); } if (StringUtils.hasText(this.url) && ! this.url.startsWith("http")) { this.url = "http://" + this.url; } String url = this.url + cleanPath(); // Get the client object from feignContext. Client = getOptional(context, client.class); // If the ribbon wraps an object, get its delegate object client.default if (Client! = null) { if (client instanceof LoadBalancerFeignClient) { client = ((LoadBalancerFeignClient)client).getDelegate(); } builder.client(client); } // Get a targeter object from feign, HystrixTargeter targeter = get(context, targeter.class); Return targeter.target(this, Builder, context, new HardCodedTarget<>(this.type, this.name, url)); }Copy the code

There are three points here that need to be read in detail

  • FeignContext object description: This class is automatically injected in the FeignAutoConfiguration class, which mainly acts as a context for feign, storing all of feign’s configuration information. Each client has a separate applicationContext (multiple interfaces with the same service name are used by the same client), parent to the Spring context, and will look for member objects in the Spring context if they are not found in their own context.
  • Feign. builder object: This class is automatically injected in the FeignAutoConfiguration auto-configuration class, which registers feign. Buidler or HystrixFeign.Buidler depending on whether hystrix is started. After obtaining builder will also object is set to its various members, the members of the object can be through a configuration file and the configuration class two configured, the default configuration file priority is greater than the configuration class, also can adjust the priority (these I do not speak out first, later I’ll share with time), members of a configuration object can be mainly include: Contract, Encoder, Decoder, Retryer, LoggerLevel, RequestInterceptor, etc
  • Client object: The Default is to use the LoadBalancerFeignClient wrapper object as the client object, which contains the ribbon processing, and finally to delegate client.Default, which uses UrlConnection to initiate requests. If the Client is a URL, the ribbon does not need to handle this. The ribbon uses client. Default as the Client object

I won’t go into the logic of the HystrixTargeter object, but the main logic is to instantiate the fuse handling class and carry it into the client object. I’m looking at the main logic, and I’ll have a special share on hystrix application later

public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget<T> Target) {// Check whether buidler is hystrixfeign.builder if (! (feign instanceof feign.hystrix.HystrixFeign.Builder)) { return feign.target(target); } feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign; SetterFactory setterFactory = getOptional(factory.getName(), context, SetterFactory.class); if (setterFactory ! = null) { builder.setterFactory(setterFactory); } // instantiate the fuse handling Class and instantiate the feign object Class<? > fallback = factory.getFallback(); if (fallback ! = void.class) { return targetWithFallback(factory.getName(), context, target, builder, fallback); } // instantiate the fuse processing factory Class and instantiate the feign object Class<? > fallbackFactory = factory.getFallbackFactory(); if (fallbackFactory ! = void.class) { return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory); }}Copy the code

The last step above calls the Builder.target method to start generating feign objects

Feign build(final FallbackFactory<? > nullableFallbackFactory) {// Configure the proxy processor factory class, Only a generated proxy processor (using the technique of anonymous classes)/super/HystrixInvocationHandler is the proxy class processor invocationHandlerFactory (new InvocationHandlerFactory() { @Override public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) { return new HystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory); }}); / / this object is used to resolve various annotations super. The contract (new HystrixDelegatingContract (contract)); Return super.build(); } / / parent feign public feign build () {/ / build Factory object method the processor (interface in each method corresponding to the processor) a method SynchronousMethodHandler. The Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel, decode404); ParseHandlersByName = new ParseHandlersByName(Contract, options, encoder, decoder, errorDecoder, synchronousMethodHandlerFactory); Return new ReflectiveFeign(handlersByName, invocationHandlerFactory); }Copy the code

The primary responsibility of the ReflectiveFeign object is to generate the proxy object

Public <T> T newInstance(Target<T> Target) { Do not share the Map < String, MethodHandler > nameToHandler = targetToHandlersByName. Apply (target); Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>(); List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>(); // If it is a native Object Method, use DefaultMethodHandler to do the same thing. 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 { methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method))); 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

The process of proxy class generation is almost finished. When we use interfaces to operate normally, we are operating HystrixInvocationHandler handler. In fact, I have a lot of things to say, such as configuration methods and priorities, parsing various MVC annotations and so on, because there are too many. Some of them I’m going to share.

Next chapter: Understanding feIGN (ii) request process parsing

The end of the term

Well everyone, the above is the whole content of this article, the novice xiaobian writing is not good, I hope to help you!!