Enable FeignClients for Feign Source Analysis

    springcloud-openfeign-core-2.1.1.release.

When using feign in spring cloud, List-1 below, the name of the FeignClient is the service name, which automatically gets the physical address from Eureka.

    List-1

@FeignClient(name="UserProvider")
public interface UserProvider {
...
}

How does this work in Springcloud?

Starting with @Enable FeignClients, List-2 below, Import annotations introduce FeignClients Registrar, which implements the ImportBean Definition Registrar interface so that spring boot handles the FeignClients Registrar.

    List-2

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

As shown in Figure 1 below, FeignClients Registrar has no complex inheritance relationship. The key class is the registerBean Definitions implementation of ImportBean Definition Registrar.

      

Figure 1

List-3 follows two steps: first, the registerDefaultConfiguration method registers the defaultConfiguration of EnableFeignClients into the Spring container; then, the registerFeignClients method registers the interface annotated by FeignClient into the Spring container.

    List-3

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

Look at the registerDefaultConfiguration method, List-4 below.

  1. Get all the properties of EnableFeignClients, and then register defaultConfiguration into the Spring container if defaultConfiguration is included
  2. Method In registerClient Configuration, a BeanDefinition of FeignClient Specification type is constructed using Builder pattern. The FeignClientSpecification implements the NamedContextFactory.Specification interface with a configuration of type name and Class<?> for the properties.

    List-4

private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
    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();
    }
    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());
}

In the registerFeignClients method, the implementation is more complex, as shown in List-5

    List-5


public void registerFeignClients(AnnotationMetadata metadata,
    BeanDefinitionRegistry registry) {
    ClassPathScanningCandidateComponentProvider scanner = getScanner();//1
    scanner.setResourceLoader(this.resourceLoader);

    Set<String> basePackages;

    Map<String, Object> attrs = metadata
            .getAnnotationAttributes(EnableFeignClients.class.getName());//2
    AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
            FeignClient.class);//3
    final Class<?>[] clients = attrs == null ? null
            : (Class<?>[]) attrs.get("clients");
    if (clients == null || clients.length == 0) {//4
        scanner.addIncludeFilter(annotationTypeFilter);
        basePackages = getBasePackages(metadata);
    }
    else {//5
        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) {
                // 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"));//6

                registerFeignClient(registry, annotationMetadata, attributes);//7
            }
        }
    }
}
  1. Get ClassPathScanner at 1 for scanning class paths
  2. Get all the properties of EnableFeignClients in two places
  3. Construct an AnnotationTypeFilter in three places. The constructor parameter is FeignClient, which is used to filter out the class containing only FeignClient.
  4. Get the client attribute value of EnableFeignClients, and if it's empty at four places, get the package path where EnableFeignClients are located (if basePackageClasses are not set)
  5. Five points, that is, the client attribute of Enable FeignClients is not empty, then traverse and put it into the collection, at the same time get the package road where the client is located and add it to the base Pacakges; construct AbstractClass Testing Type Filter, which is to add a filter condition, that is, the interface labeled with FeignClient annotations, must be in the client of Enable FeignClients.
  6. Traversing through the base packages, get the qualified classes under each package, get the corresponding beanDefinition, get the configuration value of FeignClient in six places, register it into spring container through FeignClient Specification. It is interesting to check that the class annotated by FeignClient must be an interface, otherwise the error will be reported.
  7. Seven interfaces annotated by FeignClient are encapsulated in FeignClient FactoryBean. As you all know, the interfaces in Spring are encapsulated in this one.

 

The question is, FeignClient can set up Interceptor. How can it be implemented?

This is in FeignClientFactoryBean.

Reference

  1. Source code, github address

Tags: Programming Spring Attribute github

Posted on Mon, 07 Oct 2019 09:17:34 -0700 by aneesme