Feign automatic assembly principle

spring.factories

As always, when you look at the source code, let's first look at the FeignAutoConfiguration class, which is automatically assembled under the spring.factories file. There are a few more important things

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


	@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();
		}
	}
  1. The property configurations represent the configuration classes for each Feign client, which will be mentioned again later
  2. The FeignContext bean s know by name that the Feign context contains the configuration of all feign clients
  3. Next, the two Targeter s look to see if a hystrix environment exists, and we'll also look at
  4. In addition to this class which contains HttpClient related configurations, it will not be expanded
@EnableFeignClients annotation resolution

View the automatically assembled classes, followed by the @EnableFeignClients annotation

Entering this annotation discovery, it introduces the configuration class FeignClientsRegistrar, which implements the ImportBeanDefinitionRegistrar interface, so take a look at the registerBeanDefinitions method directly from our previous experience

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

There are two steps

Register default configuration
    private void registerDefaultConfiguration(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
		// Get annotation properties for annotation @EnableFeignClients     
		Map<String, Object> defaultAttrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName(), true);

		if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
			String name;
			if (metadata.hasEnclosingClass()) {
				name = "default." + metadata.getEnclosingClassName();
			}
			else {        

				name = "default." + metadata.getClassName();
			}
			// All kinds of information ready, now register
			registerClientConfiguration(registry, name,
					defaultAttrs.get("defaultConfiguration"));
		}
	}
	private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
			Object configuration) {
		BeanDefinitionBuilder builder = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientSpecification.class);
		builder.addConstructorArgValue(name);
		builder.addConstructorArgValue(configuration);
		registry.registerBeanDefinition(
				name + "." + FeignClientSpecification.class.getSimpleName(),
				builder.getBeanDefinition());
	}

You can see that this is the default configuration for the client specified by the defaultConfiguration property on the registered @EnableFeignClients. Note that this configuration is registered for a FeignClientSpecification type of bean, which is also held by each Feign client held by the Feign context as mentioned at the beginning of this article

Register individual Feign clients
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)));
    }
 
    
    for (String basePackage : basePackages) {
        Set<BeanDefinition> candidateComponents = scanner
            .findCandidateComponents(basePackage);
        for (BeanDefinition candidateComponent : candidateComponents) {
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                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"));
                 registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }

There are three steps:

  1. Use ClassPathScanning CandidateComponentProvider to scan all interfaces labeled with the @FeignClient annotation
  2. Register the properties contained in the annotation as bean s, which are the configurations for each Feign client side
  3. Register the @Feign client

private void registerFeignClient(BeanDefinitionRegistry registry,
                                 AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    // 1. Interface name to get label @Feign annotation
    String className = annotationMetadata.getClassName();
   
    // 2. Use BeanDefinitionBuilder to construct bean s:FeignClientFactoryBean
    BeanDefinitionBuilder definition = BeanDefinitionBuilder
        .genericBeanDefinition(FeignClientFactoryBean.class);
    validate(attributes);
    
    // 3. Add properties of FeignClientFactoryBean
    definition.addPropertyValue("url", getUrl(attributes));
    definition.addPropertyValue("path", getPath(attributes));
    String name = getName(attributes);
    definition.addPropertyValue("name", name);
    definition.addPropertyValue("type", className);
    definition.addPropertyValue("decode404", attributes.get("decode404"));
    definition.addPropertyValue("fallback", attributes.get("fallback"));
    definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
    definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
 
    // 4. Set Alias
    String alias = name + "FeignClient";
    AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
 
    boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null
 
    beanDefinition.setPrimary(primary);
 
    String qualifier = getQualifier(attributes);
    if (StringUtils.hasText(qualifier)) {
        alias = qualifier;
    }
 
    // 5. Register FeignClientFactoryBean
    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
                                                           new String[] { alias });
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

     The above method registers each FeignClient as a bean: FeignClientFactoryBean in 5 steps, and I believe anyone who has read the previous article knows what the beans in the FactoryBean series do.The core of Feign's integration of Ribbon and Hystrix should also be in this class

Tags: Spring

Posted on Thu, 07 Nov 2019 21:13:15 -0500 by kycan