Distributed serial interface calls openfeign -- how is the openfeign interface injected into spring

Posted by lindm on Fri, 04 Mar 2022 05:07:25 +0100

The declarative interface calls Feign, which greatly simplifies the calls between our interfaces. The interface between our systems can be called only through annotations.

With regard to distributed, we focused on service governance. eureka, consul t and zookeeper have studied the principles and differences of the three frameworks from three perspectives. As an important part of the early spring cloud, these chapters can not be ignored in our study of distributed. As for springcloud alibaba, we have to save the first dish until the end. If you are interested in springcloud alibaba, please follow me. I will update the relevant content later

brief introduction

openfeign source code
springcloud official website

  • Feign is a client of declarative web interface call. It is based on annotation development, which greatly simplifies our development cost.

use

  • His arrival really simplifies us, and it is very convenient to integrate with him in spring cloud

pom introduction

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--openfeign-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

  • We only need to introduce openfeign, but it relies on service registration middleware. We choose spring cloud to launch service governance at the initial stage, which is also the middleware eureka discussed in the first lesson. So in addition to openfeign, we also introduced eureka. If you don't know the description of eureka, you can find it on my home page.

Start class injection

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

}

  • This is a standard springboot boot boot program,
  • ① , @ springbootapplication: springboot program ID startup annotation
  • ② , @ EnableEurekaClient: we also introduced the related configuration of eureka
  • @EnableFeignClients: enables the configuration of OpenFeign

New interface

  • In our previous case, we have two modules: payment and order. OpenFeign is used on the client. So here we continue to use the project in the eureka chapter.
  • Start eureka service and payment service first. In order to test load balancing later, we also start two payment services here.
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface OrderPaymentService {
    @RequestMapping(value = "/payment/create" , method = RequestMethod.POST)
    public ResultInfo createByOrder(Payment payment);
}

  • The content in FeignClient is the service name of payment registered with eureka. You need to pay attention here.
  • In the interface, we only need to write the corresponding method. Method names do not need to be consistent. You only need to keep the request interface and request method in the @ RequestMapping annotation consistent with that in payment.
  • The method name in the interface does not need to be processed, but the input parameter type and payment need to be consistent.

call

  • The rest is where we use it. Inject OrderPaymentService through @ Autowired and other annotations. Then there are ordinary java method calls. To demonstrate the effect of load balancing. We carry the port information in the payment method.

  • Readers can test the effect by themselves. They can find that the saved order of order service will load balance and call two payment services. The effect is the same as that of ribbon combined with restTemplate.

  • OpenFeign relies on eureka service discovery. Load balancing is realized with the help of ribbon, and interface calls are made with the help of resttemplate. In the final analysis, it is our routine operation.

Timeout control

  • In order to ensure the availability of the caller's system, we must not let OpenFeign keep waiting for the data returned by the provider and provide us with service governance based on eureka. If the address provided by eureka is stuck due to network problems, our use effect will be reduced if we keep waiting. So we need to have a timeout control. In the conventional front and back end development, the call interface also has timeout control.
  • We add a timeout interface in payment and sleep 5s inside the interface

  • Then the feign interface is developed on the order side

  • Then we call the interface on the order side and we will find that an error is reported. And the error message is a timeout error. In feign, the default timeout is 1S.

  • We only need to configure the timeout of ribbon in the configuration file.

  • Add ribbon only The readtimeout property takes effect when a timeout is found. However, it should be noted that the timeout here should be set to be a little larger than the actual timeout of the interface. Because there is network delay time in the middle. As shown in the figure below, ribbon Readtimeout = 6000, then we recommend that the sleep time in the interface be less than 4S.

  • Because openfeign is built based on hystrix. There is a thought of demotion inside. If we want to turn on hystrix, we can go through

    feign.hystrix.enabled=true
    

    To turn on hystrix.

  • The of hystrix is built into the ribbon. Hystrix is used for service degradation. The default timeout of hystrix is 1S. The default connection timeout of ribbon is 1S, and the default operation request is 1S. When the first request is sent to the server, the ribbon needs to verify the connection. So in the setting

  • h y s t r i x . t i m e o u t > 2 × ( r i b b o n . c o n n e c t T i m e o u t + r i b b o n . R e a d T i m e o u t ) hystrix.timeout>2\times(ribbon.connectTimeout+ribbon.ReadTimeout) hystrix.timeout>2×(ribbon.connectTimeout+ribbon.ReadTimeout)

  • If hystrix is enabled, we need to pay attention to the timeout control. The timeout of hystrix will be affected by the ribbon. The above formula suggests that the timeout setting of hystrix should be greater than two timeouts of ribbon. The hystrix setting is too large and meaningless, because it will be limited by the ribbon first.

feign avalanche processing fuse degradation

  • Feign timeout above, we have mentioned that feign has built-in hystrix. The function of hystrix is to serve current limiting. Then feign naturally has the function of response.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
	@AliasFor("name")
	String value() default "";
	@Deprecated
	String serviceId() default "";
	String contextId() default "";
	@AliasFor("value")
	String name() default "";
	String qualifier() default "";
	String url() default "";
	boolean decode404() default false;
	Class<?>[] configuration() default {};
	Class<?> fallback() default void.class;
	Class<?> fallbackFactory() default void.class;
	String path() default "";
	boolean primary() default true;
}
  • We can see that in addition to the service name registered by the value configuration service provider in eureka, there are also two parameters to make the fallback() and fallbackFactory() we need this time

  • These two are the schemes for configuring our service degradation processing. We have implemented fallback as an example to show the configuration of the code

@Component
public class PaymentServiceFallbackImpl implements OrderPaymentService {
    @Override
    public ResultInfo createByOrder(Payment payment) {
        ResultInfo resultInfo = new ResultInfo();
        resultInfo.setMsg("I was blown createByOrder");
        return resultInfo;
    }

    @Override
    public ResultInfo getTimeOut(Long id) {
        ResultInfo resultInfo = new ResultInfo();
        resultInfo.setMsg("I was blown getTimeOut");
        return resultInfo;
    }
}
@FeignClient(value = "CLOUD-PAYMENT-SERVICE" ,fallback = PaymentServiceFallbackImpl.class)
public interface OrderPaymentService {
    @RequestMapping(value = "/payment/create" , method = RequestMethod.POST)
    public ResultInfo createByOrder(Payment payment);

    @RequestMapping(value = "/payment/getTimeOut/{id}" , method = RequestMethod.GET)
    public ResultInfo getTimeOut(@PathVariable("id") Long id);
}
  • We started with the code above. Therefore, other configurations are not expanded here. You need to set feign. In the configuration file hystrix. Enable = true because feign turns off the of hystrix by default. Only the above two modifications are required. At the same time, I will reduce the timeout of ribbon. Simulate the phenomenon of service timeout. Before, we reported errors directly. Ribbon load error due to timeout. Now let's see what happens when we timeout.

  • In addition to fallback, there should also be a fallbackfactory. So what role do they play. Both are logic after pre fusing. However, fallback does not have the function of logging. In fallbackfactory, we can record. You can view the specific source code of factoryimpackpaymentfall. The error log can be obtained through the Throwable object.

@Component
@Slf4j
public class PaymentServiceFallbackFactoryImpl implements FallbackFactory<OrderPaymentService> {

    @Override
    public OrderPaymentService create(Throwable cause) {
        return new OrderPaymentService() {
            @Override
            public ResultInfo createByOrder(Payment payment) {
                ResultInfo resultInfo = new ResultInfo();
                resultInfo.setMsg("I was blown by the factory... createByOrder");
                return resultInfo;
            }

            @Override
            public ResultInfo getTimeOut(Long id) {
                ResultInfo resultInfo = new ResultInfo();
                resultInfo.setMsg("I was blown by the factory... getTimeOut");
                return resultInfo;
            }
        };
    }
}

Log printing

  • Since OpenFeign helps us call the interface, we certainly need to know the input and output parameters of the interface call. In other words, we need to know the details of OpenFeign calling Http.
leveleffect
NONEBy default, there is no log
BASICRequest method, URL, response status
HEADERSBASIC, request and response information
FULLComplete data
  • The configuration is also very simple. We only need to register a bean and start the log
@Configuration
public class FeignConfig {

    @Bean
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}

  • Then configure the path we need to intercept in the configuration file.
logging:
  level:
    com.zxhtom.cloud.order: debug

Principles

Preparation of prerequisite knowledge of openfeign principle (deep foundation, direct skip)

What is annotation metadata

  • It literally means metadata for annotations. In fact, it is a kind of encapsulation object for annotations. Through it, we can get the attribute data in the annotation.
Map<String, Object> defaultAttrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName(), true);

  • The above is the property content of the EnableFeignClients annotation.

Class#getEnclosingClass

  • This method is used to get the closed class of the class object. What is a closed class.
@Data
public class Parent {
    private String group;

    @Data
    class Child {
        private String name;
    }
}

  • The above is our commonly used inner class. I wonder if you have found that the creation of internal classes cannot be created elsewhere like ordinary classes. The Parent class here is the closed class of the Child class.
  • The inner class can not directly new except in its own closed class.
public static void main(String[] args) {
    Parent parent = new Parent();
    parent.setGroup("zxhgroup");
    //The following new Child will not succeed in compiling first.
    Parent.Child child = new Parent.Child();
    //It is OK to use your own closed class for new
    Parent.Child realChild = parent.new Child();
}

  • Now let's return to Class#getEnclosingClass. It will return the closed class of the current class. That is, if it is called by the class object of Child, the class object of Parent will be returned. Returns null if there is no enclosing class

  • As shown in the figure above, we finally print the Class object information of the Parent.

spring registration bean

  • I believe everyone knows that spring registered beans use BeanDefinition as the carrier. The one that really resolves BeanDefinition into spring beans is BeanDefinitionRegistry. Let's take a direct look at the following code for manually registering beans.
//First get the container context
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
//Generate BeanDefinitionBuilder corresponding to java class
BeanDefinitionBuilder builder = BeanDefinitionBuilder
        .genericBeanDefinition(Student.class);
//Register the BeanDefinition on the spring container
context.registerBeanDefinition("student",builder.getBeanDefinition());
//Try to get
Object orderController = context.getBean("student");
System.out.println(orderController);

ClassPathScanningCandidateComponentProvider

  • ClassPathScanningCandidateComponentProvider is a role that we seldom come into contact with in normal development, but it is really a role that can not be ignored in the spring source code. It is mainly used to obtain the BeanDefinition of the specified Class under the spring container
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
BeanDefinitionBuilder builder = BeanDefinitionBuilder
        .genericBeanDefinition(Student.class);
context.registerBeanDefinition("student",builder.getBeanDefinition());
Object orderController = context.getBean("student");
System.out.println(orderController);

ClassPathScanningCandidateComponentProvider classPathScanningCandidateComponentProvider = new ClassPathScanningCandidateComponentProvider(false);
classPathScanningCandidateComponentProvider.addIncludeFilter(new AnnotationTypeFilter(ComponentScan.class));
Set<BeanDefinition> candidateComponents = classPathScanningCandidateComponentProvider.findCandidateComponents("com.zxhtom.cloud.order.spring");
for (BeanDefinition candidateComponent : candidateComponents) {
    System.out.println(candidateComponent.getBeanClassName());
}


  • Above we can get to com zxhtom. cloud. order. BeanDefinition with @ ComponentScan annotation under spring package; In fact, the BeanDefinition corresponding to Config is obtained.

FeignClientFactoryBean

  • Careful developers should know that spring container managed beans are created through BeanDefinition. bean and java objects are in the same strain. java objects are represented by Class. But I don't know if you found that FeignClient actually developed an interface. But when we use it, we inject it through @ Autowired normally. This violates the design concept of spring. Similar to this is Mapper development in Mybatis.
  • I wonder if you have thought about the above situation. Spring container bean s are all generated by java objects. Why do interfaces exist in Feign or Mybatis frameworks. If we add @ Component and other annotations on the interface, we really fail to register with the spring container.
  • Yes, that's right. Feign can register the interface into spring entirely because of the FeignClientFactoryBean class. This class implements FactoryBean. The function of FactoryBean is to create a bean and register the bean into the spring container. At the same time, it will also register itself into the spring container. In other words, the FactoryBean registers two beans into the spring container. The name of FactoryBean registered is & XXX.
  • We will discuss the specific use of FactoryBean and the difference between FactoryBean and BeanFactory in the following chapters. In case you can't find me, please follow me for real-time updates

  • This interface needs to implement two methods, one of which returns the type of bean. The other is to return the bean object. Obviously, the FeignClientFactoryBean#getObject method is the real object that generates the @ FeignClient annotation, which is also called the proxy object.

OpenFeign principle analysis source code direct entry

  • Remember how we configured Feign above? We added it directly on the OrderApplication startup class. In fact, it is directly adding secondary annotations in the spring container.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
	String[] value() default {};

	String[] basePackages() default {};

	Class<?>[] basePackageClasses() default {};

	Class<?>[] defaultConfiguration() default {};

	Class<?>[] clients() default {};
}

  • The annotation on EnableFeignClients is also very simple. There are five attributes. It is worth noting that this annotation has imported feignclientsregister class . It's easy to understand that the main play must be in feignclientsregister.

  • Feignclientsregister implements resource manager, environment manager and registered bean manager. The first two well understood are the operation of resource and environment data. Registering a bean actually gives feignclientregister the ability to register a bean. We know that spring wants to register in the container through BeanDefinition. Therefore, in Feign's source tracking topic, the ImportBeanDefinitionRegistrar interface must be the top priority.

  • The ImportBeanDefinitionRegistrar interface focuses on the implementation of the registerBeanDefinitions method. Let's check this method in feignclientsregister.

regisDefaultConfiguration

  • What is the registration default configuration? This default configuration is actually the spring configuration class configured in EnableFeignClients.

  • In the previous chapter, we have analyzed the role of hasenclusingclass. Here we simply understand and judge whether EnableFeignClient is annotated on the internal class. We can see that the final call in the actual regisDefaultConfiguration method is registerclientconfiguration. In registerclientconfiguration, FeignClientSpecification is actually registered in the spring container.

  • FeignClientSpecification implements namedcontextfactory Specification interface.

  • NamedContextFactory.Specification is used to manage all specifications in the spring container. The function of this class is to let the AnnotationConfigApplicationContext manage the corresponding Config according to different name s. This is the logic in regisDefaultConfiguration. In the use of OpenFeign in our first chapter, we did not configure anything in the @ EnableFeignClients annotation. Later, we will continue to explore in the expansion

registerFeignClients

  • Next, let's explore the logic of the method registerFeignClients.
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);

  • At the beginning, the class ClassPathScanningCandidateComponentProvider mentioned in the previous chapter of pre storage knowledge is also mentioned. Here is to create the scanned object. It is convenient to scan FeignClient annotation class for parsing later
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());

  • Next is MetaData. This MetaData is generated by the OrderApplication startup class. Because this is entered by the EnableFeignClients annotation on the startup class. This step is to get the attribute value of the EnableFeignClients annotation. Here is the same as where registerDefaultConfiguration is used to obtain the relevant configuration.

  • The latter is configured according to the EnableFeignClients annotation attribute. Will get the attribute clients. According to the following if judgment, it can be inferred that the clients attribute is used to scan the path of the package where the Client is located. And add the ClassPathScanningCandidateComponentProvider filter.
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);

  • The next step is to get the BeanDefinition. Use findCandidateComponents to obtain the BeanDefinition corresponding to the class with EnableFeignClients annotation under the specified package path
Map<String, Object> attributes = annotationMetadata
    .getAnnotationAttributes(
        FeignClient.class.getCanonicalName());

String name = getClientName(attributes);
registerClientConfiguration(registry, name,
    attributes.get("configuration"));

  • Then register the name of the bean according to the attribute on the FeignClient annotation, and then register it in the spring container through the BeanDefinition.

  • Finally, register the objects in FeignClientFactoryBean to the spring container. The object generated by FeignClientFactoryBean is the class information annotated by @ FeignClient. Register the object generated through FeignClientFactoryBean through metadata information.

  • As mentioned in the above reserve chapter, FeignClientFactoryBean is the proxy object of the interface that generates FeignClient annotations. When we @ Autowired, the injected object is actually the proxy object. The proxy object will parse the real service collection based on the annotation information, and then make interface calls based on load balancing.

summary

Let me see the source code

  • openfeign greatly simplifies the coupling of our interface calls. We need to configure relevant information in the main interface. Then there is the localized call method.
  • But the implementation of openfeign is worthy of our need. It involves the bean registration of spring. Bean interception. Dynamic agent and other logic.
  • Due to the problem of time, space and ability, this chapter only stops at the dynamic agent of openfeign.

ClientFactoryBean ` generates objects and registers them.

  • As mentioned in the above reserve chapter, FeignClientFactoryBean is the proxy object of the interface that generates FeignClient annotations. When we @ Autowired, the injected object is actually the proxy object. The proxy object will parse the real service collection based on the annotation information, and then make interface calls based on load balancing.

[external chain picture transferring... (img-ffZ9BMKn-1618749155374)]

summary

Let me see the source code

  • openfeign greatly simplifies the coupling of our interface calls. We need to configure relevant information in the main interface. Then there is the localized call method.
  • But the implementation of openfeign is worthy of our need. It involves the bean registration of spring. Bean interception. Dynamic agent and other logic.
  • Due to the problem of time, space and ability, this chapter only stops at the dynamic agent of openfeign.

[external chain picture transferring... (img-NL4AysG9-1618749155374)]

Topics: Distribution Spring Cloud source code OpenFeign