Spring cloud Feign + Hystrix principle

Keywords: Spring SpringBoot

Principle of Spring cloud Feign + Hystrix (I)

Netflix Hystrix has stopped updating and is currently in maintenance status. In spring cloud, the call to the service is usually completed with feign. If you want to use hystrix as the fuse at the same time, you only need to configure as follows (provided that feign dependency has been introduced)

  • Introducing spring cloud hystrix starter dependency
  • stay application.properties Add feignclient enable hystrix configuration in
  • Add @ EnableHystrix annotation on the startup main class
  • Just configure the fallback class on the feign interface
 <dependency>   
 	<groupId>org.springframework.cloud</groupId>      
 	<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>      
 </dependency>

feign.hystrix.enbaled=true

@EnableHystrix
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class ConsumerApplication {
	public static void main(String[] args) {
		SpringApplication.run(ConsumerApplication.class, args);
	}
}
@Component
public class UserFallBack implements UserFeignClient{
    @Override
    public String getUser() {
        return "fall back";
    }
}

@FeignClient(value = "eureka-provider",fallback = UserFallBack.class)
public interface UserFeignClient {

    @GetMapping("/user")
    String getUser();
    
}

With the above configuration, Feign client with hystrix can be used. This paper focuses on the integration of Feign and hystrix to see how its principle is analyzed in the following order

  • How EnableHystrix works
  • Analysis of HystrixTargeter in feign autoconfiguration
  • FeignClientsConfiguration Feign.Builder analysis
  • loadBalance analysis in FeignClientFactoryBean

EnableHystrix analysis

First of all, look at the annotation definition. It is found that enable circuit breaker is enabled in the annotation. This annotation is the key to scan Hystrix related beanDefinition to spring container

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@EnableCircuitBreaker
public @interface EnableHystrix {
}

The familiar Import annotation appears in the enablechircuitbreaker interface. In this annotation, the enablechircuitbreakerinportselector class will complete the bean scanning

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({EnableCircuitBreakerImportSelector.class})
public @interface EnableCircuitBreaker {
}

Next, analyze the working principle of enablechircuitbreakerinportselector. First, enter enablechircuitbreakerinportselector and find that there is only one isEnable method and it is on by default,
At this time, the selectImports method does not have the method in the Override parent class to directly view the method in the parent class

@Order(2147483547)
public class EnableCircuitBreakerImportSelector extends SpringFactoryImportSelector<EnableCircuitBreaker> {
    public EnableCircuitBreakerImportSelector() {
    }

    protected boolean isEnabled() {
        return (Boolean)this.getEnvironment().getProperty("spring.cloud.circuit.breaker.enabled", Boolean.class, Boolean.TRUE);
    }
}

Because enable defaults to true, the most important method to execute else is s pringFactoriesLoader.loadFactoryNames , If you have seen the source code of springboot to start the main class, the function of this method is to add meta-inf in the jar package/ spring.factories Parse and add it to the container. If the HystrixCircuitBreakerConfiguration is the hystrix core configuration class, the related bean s will be initialized. You can refer to the related classes for checking

LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(this.annotationClass, this.beanClassLoader)));

public String[] selectImports(AnnotationMetadata metadata) {
        if (!this.isEnabled()) {
            return new String[0];
        } else {
            AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(this.annotationClass.getName(), true));
            Assert.notNull(attributes, "No " + this.getSimpleName() + " attributes found. Is " + metadata.getClassName() + " annotated with @" + this.getSimpleName() + "?");
            List<String> factories = new ArrayList(new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(this.annotationClass, this.beanClassLoader)));
            if (factories.isEmpty() && !this.hasDefaultFactory()) {
                throw new IllegalStateException("Annotation @" + this.getSimpleName() + " found, but there are no implementations. Did you forget to include a starter?");
            } else {
                if (factories.size() > 1) {
                    this.log.warn("More than one implementation of @" + this.getSimpleName() + " (now relying on @Conditionals to pick one): " + factories);
                }

                return (String[])factories.toArray(new String[factories.size()]);
            }
        }
    }

FeignAutoConfiguration

The target is generated in this class according to the existence of classpath feign.hystrix.HystrixFeign Class store generates HystrixTargeter

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
protected static class HystrixFeignTargeterConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public Targeter feignTargeter() {
			return new HystrixTargeter();
		}
}

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public Targeter feignTargeter() {
			return new DefaultTargeter();
       }
}

FeignClientsConfiguration

This is mainly based on applicaton.yml or application.properties Is the following configuration available

feign.hystrix.enabled = true

If classpath exists at the same time HystrixCommand.class, HystrixFeign.class be Feign.Builder by HystrixFeign.builder()

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
protected static class HystrixFeignConfiguration {

		@Bean
		@Scope("prototype")
		@ConditionalOnMissingBean
		@ConditionalOnProperty(name = "feign.hystrix.enabled")
		public Feign.Builder feignHystrixBuilder() {
			return HystrixFeign.builder();
		}

	}

So far, the basic loading of Hystrix dependency is completed

FeignClientFactoryBean

Finally, when getting the FeignClient object, the getTarget method will be called through the FeignClientFactoryBean class getObject method. This method will look for Feign.Builder That is to say, the integration of FeignClient and Hystrix has been completed

<T> T getTarget() {
		FeignContext context = this.applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);

		if (!StringUtils.hasText(this.url)) {
			if (!this.name.startsWith("http")) {
				this.url = "http://" + this.name;
			}
			else {
				this.url = this.name;
			}
			this.url += cleanPath();
			return (T) loadBalance(builder, context,
					new HardCodedTarget<>(this.type, this.name, this.url));
		}
		.........Omit some codes.................
	}

summary

In fact, the integration process is not complicated. In the next article, we will continue to analyze the working principle of Hystrix. If you are interested in the details of the integration process, you can debug it in a single step

Posted by Silver_Eclipse on Thu, 25 Jun 2020 00:49:03 -0700