Feign remotely invokes the component

0. There are problems with the existing invocation methods

//http://localhost:8090/autodeliver/checkState/1545133
@GetMapping("/checkState/{userId}")
public Integer findResumeOpenState(@PathVariable Long userId){
    String url="http://lagou-service-resume/resume/openstate/" + userId;
    Integer forObject = restTemplate.getForObject(url ,Integer.class);
    return forObject;
}
Copy the code

There are inconveniences

  • 1) Concatenate url
  • 2) restTmplate getForObJect

So both of these things are more tempestuated, so let me just write this tempestuated thing and on the other hand, concatenation urls are very low, concatenation strings, concatenation parameters, very low and error prone

1. Introduction to Feign

Feign is a lightweight RESTful HTTP client developed by Netflix that invokes HTTP requests as Java interface annotations rather than directly encapsulating HTTP request packets as Java does. Feign is widely used in Spring Cloud solutions. Similar to Dubbo, the service consumer takes the service provider’s interface and invokes it as if it were a local interface method, actually making a remote request.

  • Feign makes it easy and elegant to call the HTTP API: Instead of concatenating the url and calling the API of the restTemplate, in SpringCloud it’s very simple to use Feign, create an interface (on the consumer-service caller side), add some annotations to the interface and the code is done

  • SpringCloud has enhanced Feign to support SpringMVC annotations (OpenFeign)

Essence: Encapsulates the Http call flow, more in line with the interface oriented programming habits, similar to Dubbo service call

The way Dubbo is called is really good interface oriented programming

2. Feign configuration application

We created a resume delivery microservice, Lagou-service-autoDeliver -8096

(1) Pom. XML configuration file, here we focus on adding spring-Cloud-starter-OpenFeign

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>
Copy the code

(2) new start class AutodeliverApplicaton8096, marked with annotations @ EnableFeignClients, enable Feign client, because we don’t use RestTemplete, so there is no injection

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

Note: Remove the Hystrix fuse support annotation @enablecircuitbreaker at this time, because Feign will be introduced automatically and does not need to be specified explicitly

Next step to the core, create a Service ResumeServiceFeignClient similar to Dubbo. The interface content here is the RestFul interface definition of the automatic delivery microservice

// The original call method
//String url="http://lagou-service-resume/resume/openstate/" + userId;
// Indicate that the current class is a Feign client and value specifies the name of the service that the client is requesting (the name of the service provider registered in the registry)
@FeignClient(value = "lagou-service-resume")
@RequestMapping("/resume")
public interface ResumeServiceFeignClient {

    // The request path for the call
    //@RequestMapping(value = "/openstate/{userId}",method= RequestMethod.GET)
    @GetMapping("/openstate/{userId}")
    // Value =userID ==> must be written
    public Integer findResumeOpenState(@PathVariable(value = "userId") Long userId);
}

Copy the code

Note:

  • The name attribute of the @FeignClient annotation is used to specify the name of the service provider to invoke, consistent with spring.application.name in the service provider YML file

  • The interface method in the interface is like the Hander method in the remote service provider Controller (except that it is called locally), so the parameter binding can use @pathVariable, @requestParam, @requestheader, etc. This is also OpenFeign’s support for SpringMVC annotations, but note that value must be set otherwise an exception will be thrown

(4) Transform AutoDeliverController, directly inject interface, can complete the call

@RestController @RequestMapping("/autodeliver") public class AutoDeliverController { @Autowired private ResumeServiceFeignClient client; @GetMapping("/checkState/{userId}") public Integer findResumeOpenState(@PathVariable Long userId){ return client.findResumeOpenState(userId); }}Copy the code

Feign support for load balancing

Timeout phenomenon

First of all, the port 8080 service has been set to sleep for a period of 10 seconds

Port 8081 service, normal fast return

When we call the Feign client, we always return 8081. Why?

The request actually reached port 8080 because of Feign’s default request processing timeout1sAfter a second, the Ribbon load balancing mechanism is at work, but the Ribbon does not return the request

Load Balancing Configuration

  • When a failed request is reached, it tries to access the current instance again (the number of times is specified by MaxAutoRetries).
  • If not, another instance is accessed, and if not, another instance is accessed (the number of changes is configured by MaxAutoRetriesNextServer),
  • If it still fails, a failure message is returned.

# for the microservice name of the called party, if not added, it takes effect globally
lagou-service-resume:
  ribbon:
   Request connection timeout
   ConnectTimeout: 2000

   Request processing timeout
   ReadTimeout: 15000

   All operations are retried
   OkToRetryOnAllOperations: true
 
   MaxAutoRetries: 0 Number of retries for the currently selected instance, not including the first call
   MaxAutoRetriesNextServer: 0 Number of retries for switching instances
   NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule Load policy adjustment

   # # NFLoadBalancerRuleClassName: com.net flix. Loadbalancer. # RandomRule load strategy adjustment

Copy the code

Feign support for fuses

(1) Enable Feign’s support for fuses in the Feign client project profile (application.yML)

# Enable Feign's fusing function
feign:
  hystrix:
   enabled: true
Copy the code

(2) A failure callback function was configured in Hystrix to be called automatically when the service was degraded, so how will it be called in Feign? The fallback logic requires defining a class that implements the FeignClient interface and implements the methods in the interface

@Component
public class ResumeFallback implements ResumeServiceFeignClient {
    @Override
    public Integer findResumeOpenState(Long userId) {
        return -11; }}Copy the code

(3) In the @feignClient annotation, configure the callback method’s class name fallback = ResumeFallback

@FeignClient(value = "lagou-service-resume",fallback = ResumeFallback.class,path = "/resume")
//@RequestMapping("/resume")
public interface ResumeServiceFeignClient {

    // The request path for the call
    //@RequestMapping(value = "/openstate/{userId}",method= RequestMethod.GET)
    @GetMapping("/openstate/{userId}")
    // Value =userID ==> must be written
    public Integer findResumeOpenState(@PathVariable(value = "userId") Long userId);
}

Copy the code

(4) At this point we request the call again and find that the service is degraded immediately

Here we have a question. Didn’t we just set the timeout at the Ribbon to 15,000 seconds? Why didn’t it work?

The answer is: When we enable Hystrix, hystrix also has a timeout, and by default, it is set to a very short time, which we can change through configuration.

# Enable Feign's fusing function
feign:
  hystrix:
   enabled: true
   
hystrix:
  command:
   default:
    execution:
     isolation:
      thread:
       # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Hystrix timeout value Settings
       timeoutInMilliseconds: 15000
Copy the code

Pay attention to the point

  • Once Hystrix is turned on, all methods in Feign are managed and the corresponding fallback logic is used in the event of a problem

  • For timeout, there are currently two timeout Settings (Feign/ Hystrix). Fuses are performed according to the minimum of these two timeout Settings. If the processing time exceeds the minimum timeout, fuses enter the fallback and downgrade logic

Feign support for request compression and response compression

Feign supports GZIP compression of requests and responses to reduce performance losses during communication. To enable request and response compression, use the following parameters:

feign:
 compression:
  request:
   enabled: true # enable request compression
   mime-types: text/html,application/xml,application/json Set the data type to be compressed. This is also the default value
   min-request-size: 2048 # set the minimum size for triggering compression. This is also the default value
  response:
   enabled: true Turn on response compression
Copy the code

6. Feign log level configuration

(1) Create a log configuration class FeignConfig

@Configuration
public class FeignConfig {
    @Bean
    Logger.Level feignLevel(a) {
        returnLogger.Level.FULL; }}Copy the code

(2) Modify application. Yml file to add log function. Here need to configure the Feign client service the fully qualified class name of the interface com.lagou.edu.service.ResumeServiceFeignClient, shows that we are on this Feign monitor client logs

logging:
  level:
  # Feign Logs respond only to logs with the log level debug
   com.lagou.edu.service.ResumeServiceFeignClient: debug
Copy the code

(3) Log details can be found when the interface is called again

Feign core source code analysis

@EnableFeignClients

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # org.springframework.cloud.openfeign.EnableFeignClients # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
	String[] value() default {};
	String[] basePackages() default {};
}
Copy the code

Can see FeignClientsRegistrar ImportBeanDefinitionRegistrar interface as long as implements this interface is achieved, then the spring at startup, can call this method, to register the bean, So let’s focus on implementing the registerBeanDefinitions method

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar.ResourceLoaderAware.EnvironmentAware {}public interface ImportBeanDefinitionRegistrar {
	void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
Copy the code

Let’s look at the registerBeanDefinitions method

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar.ResourceLoaderAware.EnvironmentAware {

    @Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {# # = = = > the global default configuration into container registerDefaultConfiguration (metadata registry); ## ===> marked@FeignClientClass create object injected into container -->>>> for added@FeignClentAnnotated interface operations registerFeignClients(metadata, registry); }}Copy the code

RegisterDefaultConfiguration registered the default configuration

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # org.springframework.cloud.openfeign.FeignClientsRegistrar#registerDefaultConfiguration # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #private void registerDefaultConfiguration(AnnotationMetadata metadata,BeanDefinitionRegistry registry) From the metadata, According to the type name for Map collections Map < String, Object > defaultAttrs = metadata. GetAnnotationAttributes (EnableFeignClients. Class. GetName (),true); ##===> contains defaultConfigurationif(defaultAttrs ! =null && defaultAttrs.containsKey("defaultConfiguration")) {
      String name;
      if (metadata.hasEnclosingClass()) {
          name = "default." + metadata.getEnclosingClassName();
      }
      else {
          name = "default."+ metadata.getClassName(); } # # = = = > the registration type registerClientConfiguration (registry, name, defaultAttrs. Get ("defaultConfiguration")); }}Copy the code

RegisterFeignClients Method of registering Feign clients

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClients # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #public void registerFeignClients(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {## ===> Defines a scanner that is primarily intended to scan@FeignClient
  ClassPathScanningCandidateComponentProvider scanner = getScanner();
  scanner.setResourceLoader(this.resourceLoader); Set<String> basePackages; Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName()); AnnotationTypeFilter annotationTypeFilter =new AnnotationTypeFilter(FeignClient.class);
  finalClass<? >[] clients = attrs ==null ? null: (Class<? >[]) attrs.get("clients");
  if (clients == null || clients.length == 0) { scanner.addIncludeFilter(annotationTypeFilter); ## ===> if we annotate@FeignClientBasePackages = getBasePackages(metadata) basePackages = getBasePackages(metadata) basePackages = getBasePackages } ## ===> traverse all package pathsfor(String basePackage : BasePackages) {## ===> Based on the package path, Get BeanBefinition collection Set < BeanDefinition > candidateComponents = scanner. FindCandidateComponents (basePackage); ## ===> Traversal BeanBefinition collectionfor (BeanDefinition candidateComponent : candidateComponents) {
              if (candidateComponent instanceof AnnotatedBeanDefinition) {
                  // verify annotated class is an interface
                  AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                  AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                  Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface"); From the metadata, Obtain feature Map based on FeignClient type Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName()); String name = getClientName(attributes); # # = = = > access@FeignClientThe configuration of the register registerClientConfiguration (registry, name, attributes. Get ("configuration")); RegisterFeignClient (Registry, annotationMetadata, attributes); }}}}Copy the code

Take a closer look at the registerFeignClient method

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClient # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map
       
         attributes)
       ,> { String className = annotationMetadata.getClassName(); So what you can see here is that you have a FeignClientFactoryBean object, it's a FactoryBean, so up here, when you actually use it, the object that you get from the container, is you use factoryBean.getobject to return the object, This object is the object of the corresponding interface class BeanDefinitionBuilder Definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class); validate(attributes); definition.addPropertyValue("url", getUrl(attributes));
    definition.addPropertyValue("path", getPath(attributes)); 
    
    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,  newString[] { alias }); The last call to register the object is the registerBeanDefinition method. At the same time will trigger FactoryBean. GetObject method BeanDefinitionReaderUtils. RegisterBeanDefinition (holder, registry); }Copy the code

Let’s go inside the FeignClientFactoryBean and find the getObject method

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # org.springframework.cloud.openfeign.FeignClientFactoryBean#getObject # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #@Override
public Object getObject(a) throws Exception {
    return getTarget();
}
Copy the code

Into thegetTargetMethod can be found after

Enter the loadBalance method

Go to the target method

Enter the build method

Here is the newInstance() method after build

Insight into the factory. The create method, we can see from here, eventually return is ReflectiveFeign FeignInvocationHandler

Enter the FeignInvocationHandler method

Enter the Invoke method, which is triggered whenever the dynamic proxy executes

Enter the executeAndDecode method

The execute method of the client is displayed

Enter the executeWithLoadBalancer method

Go to the Submit method

Go to the selectServer method

Enter the getServerFromLoadBalancer method,

SpringCloud Ribbon load balancing Ribbon load balancing Ribbon load balancing Ribbon load balancing

The source code example