Feign集成Hystrix源碼分析(自動配置)


緣起:我司項目中集成了Feign與Hystrix全局降級處理,於是從源碼層面開始分析,自定義處理是怎麼運行的

@EnableFeignClients與@FeignClient

@EnableFeignClients -> FeignClientsRegistrar 掃描 @Feign註解的類

@FeignClient客戶端

參數配置

@FeignClient(contextId = "remoteBizMaintainConfig", value = ServiceNameConstants.MAINTENANCE_SERVICE)
public interface RemoteBizMaintainConfig {

	/**
	 * 新增保養配置
	 *
	 * @param bizMaintainConfig
	 * @return
	 */
	@PostMapping("/bizmaintainconfig/save")
	R save(@Valid @RequestBody BizMaintainConfig bizMaintainConfig);
}

@EnableFeignClients開啓Feign

在啓動類上加上@EnableFeignClients,點進去,可以看到通過@Import注入了FeignClientsRegistrar

@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 {};

}

FeignClientsRegistrar註冊客戶端

可以看到FeignClientsRegistrar實現了ImportBeanDefinitionRegistrar,我在之前的spring源碼章節有詳細介紹spring是怎麼處理@importspring加載流程之ConfigurationClassPostProcessor
在這裏插入圖片描述
該類主要實現了registerBeanDefinitions方法

@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		// 註冊@EnableFeignClients中的defaultConfiguration,默認爲空
		registerDefaultConfiguration(metadata, registry);
		// 註冊帶有@FeignClient的類
		registerFeignClients(metadata, registry);
	}

主要registerFeignClients(metadata, registry)

public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		// 定義scanner用於掃描
		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);

		Set<String> basePackages;
	
		// 獲取@EnableFeignClients的參數
		Map<String, Object> attrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName());
				
		// 設置掃描過濾器,包含@FeignClient
		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
				FeignClient.class);
		final Class<?>[] clients = attrs == null ? null
				: (Class<?>[]) attrs.get("clients");
		if (clients == null || clients.length == 0) {
		 	// 添加掃描過濾器
			scanner.addIncludeFilter(annotationTypeFilter);
			// 獲取掃描的包名
			basePackages = getBasePackages(metadata);
		}
		else {
			// 一般不通過參數clients來配置掃描範圍,需要的可具體再看
			.
			.
			.
		}

		// 循環需要掃描的包名
		for (String basePackage : basePackages) {
			// 掃描該包下帶有@FeignClient的所有類
			Set<BeanDefinition> candidateComponents = scanner
					.findCandidateComponents(basePackage);
			
			// 循環帶有@FeignClient的類
			for (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");

					// 獲取@FeignClient參數
					Map<String, Object> attributes = annotationMetadata
							.getAnnotationAttributes(
									FeignClient.class.getCanonicalName());

					// 獲取BeanName
					String name = getClientName(attributes);
					// 暫時不清楚註冊這幹嘛用的
					registerClientConfiguration(registry, name,
							attributes.get("configuration"));
					// 註冊帶有@FeignClientBean
					registerFeignClient(registry, annotationMetadata, attributes);
				}
			}
		}
	}

上面一些方法,比如:

  • basePackages = getBasePackages(metadata)
  • name = getClientName(attributes);

通過源碼可以加深瞭解怎麼配置參數
例如:優先通過contextId獲取BeanName

private String getClientName(Map<String, Object> client) {
		if (client == null) {
			return null;
		}
		String value = (String) client.get("contextId");
		if (!StringUtils.hasText(value)) {
			value = (String) client.get("value");
		}
		if (!StringUtils.hasText(value)) {
			value = (String) client.get("name");
		}
		if (!StringUtils.hasText(value)) {
			value = (String) client.get("serviceId");
		}
		if (StringUtils.hasText(value)) {
			return value;
		}

		throw new IllegalStateException("Either 'name' or 'value' must be provided in @"
				+ FeignClient.class.getSimpleName());
	}

FeignClientFactoryBean註冊的bean類型

最後執行registerFeignClient時,通過FeignClientFactoryBean來定義Bean

private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
		String className = annotationMetadata.getClassName();
		// 先獲取Bean定義,在配置Bean
		BeanDefinitionBuilder definition = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientFactoryBean.class);
		validate(attributes);
		definition.addPropertyValue("url", getUrl(attributes));
		definition.addPropertyValue("path", getPath(attributes));
		.
		.
		.
		// 註冊Bean
	}

FeignClientFactoryBean實現了FactoryBean,所有實現了該接口的Bean都是工廠Bean,通過重寫getObject()方法返回真正的Bean。這一點跟mybatis掃描註冊mapper接口一樣,都是實現了FactoryBeanmybatis與spring的整合之MapperFactoryBean

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

<T> T getTarget() {
		FeignContext context = this.applicationContext.getBean(FeignContext.class);
		// 獲取容器中定義好的Feign.Builder,同時獲取feign的yml配置
		Feign.Builder builder = feign(context);

		// 一般不配置url,配置name或value(服務名稱),交由erreka調用
		if (!StringUtils.hasText(this.url)) {
			if (!this.name.startsWith("http")) {
				// 帶上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));
		}
		if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
			this.url = "http://" + this.url;
		}
		String url = this.url + cleanPath();
		Client client = getOptional(context, Client.class);
		if (client != null) {
			if (client instanceof LoadBalancerFeignClient) {
				// not load balancing because we have a url,
				// but ribbon is on the classpath, so unwrap
				client = ((LoadBalancerFeignClient) client).getDelegate();
			}
			builder.client(client);
		}
		Targeter targeter = get(context, Targeter.class);
		return (T) targeter.target(this, builder, context,
				new HardCodedTarget<>(this.type, this.name, url));
	}
	
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
			HardCodedTarget<T> target) {
		Client client = getOptional(context, Client.class);
		if (client != null) {
			builder.client(client);
			// 獲取容器中定義好的Targeter
			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?");
	}

看到最後通過getObject()返回的BeanDefinition其實是targeter.target(...),就是帶有@FeignClient類的正真Bean實例

interface Targeter {

	<T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
			FeignContext context, Target.HardCodedTarget<T> target);

}

它,是一個接口,那麼Feign內部肯定有默認的實現

FeignClientsConfiguration自動配置類

Feign.Builder

需要設置feign.hystrix.enabledtrue開啓Hystrix,否則用不到降級配置
這裏注入的Feign.BuilderFeignClientFactoryBean被用到了

@Configuration
public class FeignClientsConfiguration {
    
            
    @Bean
    @ConditionalOnMissingBean
    public Retryer feignRetryer() {
        return Retryer.NEVER_RETRY;
    }

    @Bean
    @Scope("prototype")
    @ConditionalOnMissingBean
    public Feign.Builder feignBuilder(Retryer retryer) {
        return Feign.builder().retryer(retryer);
    }
    
    @Configuration
    @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();
        }
    }
}

FeignAutoConfiguration自動配置類

裝配FeignContextTargeter,在FeignClientFactoryBean會被用到了

@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
		FeignHttpClientProperties.class })
public class FeignAutoConfiguration {

	@Autowired(required = false)
	private List<FeignClientSpecification> configurations = new ArrayList<>();

	@Bean
	public HasFeatures feignFeature() {
		return HasFeatures.namedFeature("Feign", Feign.class);
	}

	@Bean
	public FeignContext feignContext() {
		FeignContext context = new FeignContext();
		context.setConfigurations(this.configurations);
		return context;
	}

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

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

	}

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

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

	}

同時看到會去加載feign配置

@EnableConfigurationProperties({ FeignClientProperties.class,
		FeignHttpClientProperties.class })

FeignClientProperties

讀取feign配置

# feign 配置
feign:
  hystrix:
    enabled: true
  okhttp:
    enabled: true
  httpclient:
    enabled: false
  client:
    config:
      default:
        connectTimeout: 10000
        readTimeout: 10000

FeignHttpClientProperties

FeignHttpClientProperties是加載feign.httpclient配置的,因爲用的是okhttp,所以禁用了httpclient
以後抽時間瞭解下okhttphttpclient

DefaultTargeter

沒有做任何處理,這是沒有配置Hystrix時的默認實現類

@Override
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
			FeignContext context, Target.HardCodedTarget<T> target) {
		return feign.target(target);
	}

下章節,Targeter的其他實現以及自定義實現類Feign集成Hystrix源碼分析(Targeter實現類)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章