feign【0】bean装配

293 阅读3分钟

这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战

前言

在自动装配中,我们是用@EnableFeignClient作为例子的。

在自动装配中,我们可以知道:

  • 具体定义的feign,在FeignClientsRegistrar中被定义为factoryBean,并将beanDefinition放入context中。

这里我们来看看这些bean是如何装配的。

版本:openFeign 2.2.0

接口装配

入口在这里:

public void registerBeanDefinitions(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
   registerDefaultConfiguration(metadata, registry);
   registerFeignClients(metadata, registry);
}

	public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		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);
		final Class<?>[] clients = attrs == null ? null
				: (Class<?>[]) attrs.get("clients");
		if (clients == null || clients.length == 0) {
			scanner.addIncludeFilter(annotationTypeFilter);
			basePackages = getBasePackages(metadata);
		}
		else {
			final Set<String> clientClasses = new HashSet<>();
			basePackages = new HashSet<>();
			for (Class<?> clazz : clients) {
				basePackages.add(ClassUtils.getPackageName(clazz));
				clientClasses.add(clazz.getCanonicalName());
			}
			AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
				@Override
				protected boolean match(ClassMetadata metadata) {
					String cleaned = metadata.getClassName().replaceAll("\\$", ".");
					return clientClasses.contains(cleaned);
				}
			};
			scanner.addIncludeFilter(
					new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
		}
		//在这里,会从包根目录中读取符合我们定义的那些feign(用@FeignClient标注的)
		for (String basePackage : basePackages) {
			Set<BeanDefinition> candidateComponents = scanner
					.findCandidateComponents(basePackage);
			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");
			//在这里,会从注解上读取相关的配置信息
					Map<String, Object> attributes = annotationMetadata
							.getAnnotationAttributes(
									FeignClient.class.getCanonicalName());

					String name = getClientName(attributes);
					registerClientConfiguration(registry, name,
							attributes.get("configuration"));
		//根据上面读取到的信息,构造feignClient的BD
					registerFeignClient(registry, annotationMetadata, attributes);
				}
			}
		}
	}

而这里beanDefinition的装配,实际是将值都放入了BeanDefinitionpropertyValues中。

  • 这里的代理类,是FeignClientFactoryBean

注意:此时的url,如果没有显式标注,是没有的,如果要正常使用我们得去看具体实例化的地方。

既然是factoryBean,那么必然有一个getobject的方法,并且这个bean就是注入到对应类上的实例(没有初始化等中间点来做代理等操作的话):

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

	<T> T getTarget() {
        //这里的feignContext是在FeignAutoConfiguration中作为bean注入的
		FeignContext context = this.applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);
		//这里就是对应着我们的一般情况:用eureka或者nacos之类的注册中心,没有写url的情况
		if (!StringUtils.hasText(this.url)) {
			if (!this.name.startsWith("http")) {
                //请求的url就会变成:http://我们指定的名字
				this.url = "http://" + this.name;
			}
			else {
				this.url = this.name;
			}
			this.url += cleanPath();
            //【1】
			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));
	}
  • 【1】我们知道openFeign和hytrix整合了,因此看这里的是loadBalance:

    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 = 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?");
    }
    
    //这里依然是从context中拿实例,ceontext在加载的时候已把对应的feign都预先准备好了
    	protected <T> T getOptional(FeignContext context, Class<T> type) {
    		return context.getInstance(this.contextId, type);
    	}
    
    //上面的get方法,同样地target是从context中取的
    	protected <T> T get(FeignContext context, Class<T> type) {
    		T instance = context.getInstance(this.contextId, type);
    		if (instance == null) {
    			throw new IllegalStateException(
    					"No bean found of type " + type + " for " + this.contextId);
    		}
    		return instance;
    	}
    
    //target.target:(HystrixTargeter)
    //TODO
    	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
    			FeignContext context, Target.HardCodedTarget<T> target) {
    		if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
    			return feign.target(target);
    		}
    		feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
    		String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName()
    				: factory.getContextId();
    		SetterFactory setterFactory = getOptional(name, context, SetterFactory.class);
    		if (setterFactory != null) {
    			builder.setterFactory(setterFactory);
    		}
    		Class<?> fallback = factory.getFallback();
    		if (fallback != void.class) {
    			return targetWithFallback(name, context, target, builder, fallback);
    		}
    		Class<?> fallbackFactory = factory.getFallbackFactory();
    		if (fallbackFactory != void.class) {
    			return targetWithFallbackFactory(name, context, target, builder,
    					fallbackFactory);
    		}
    
    		return feign.target(target);
    	}
    
    • 而这里的feignContext中的target,其实是在FeignAutoConfiguration中配置的:

      @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();
         }
      
      }
      

      可以看到,这里提供了2个targeter,如果带了hytrix,那么这个feignTargeter就是带负载均衡的,否则就是默认的。

      脑筋急转弯:这里的getInstance是根据@Bean的类型来取的bean。

      这里的@Configuration,是在扫描的时候被自动注入器(autoImportSelector)带入到Spring容器中的。

      如果要开启hystrix的使用,那么需要在启动类上标注:@EnableCircuitBreaker,否则用的就是普通的feign。