FeignClientFactoryBean for Feign Source Analysis

     springcloud-openfeign-core-2.1.1.release.

Continue the previous article Enable FeignClients for Feign Source Analysis Later, the interface of FeignClient annotation is encapsulated in FeignClient FactoryBean in Spring cloud. Now let's look at the implementation of FeignClient FactoryBean.

                 

Figure 1

The attributes are shown in List-1, and the values of these attributes are set in the FeignClients Registrar.

    List-1

class FeignClientFactoryBean
		implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {

	/***********************************
	 * WARNING! Nothing in this class should be @Autowired. It causes NPEs because of some
	 * lifecycle race condition.
	 ***********************************/

	private Class<?> type;

	private String name;

	private String url;

	private String contextId;

	private String path;

	private boolean decode404;

	private ApplicationContext applicationContext;

	private Class<?> fallback = void.class;

	private Class<?> fallbackFactory = void.class;
...

Because of the implementation of the FactoryBean interface, let's look at the most important getObject() methods, such as List-2, in which the getTarget method first gets the FeignContext from the Spring context. The FeignContext is registered in the FeignAutoConfiguration in the Spring container. As shown in List-3, all FeignClient Specifications of the spring container are put into the FeignContext, and the FeignClient Specification is in the FeignContext. Enable FeignClients for Feign Source Analysis As mentioned above, default Configuration of Enable FeignClients.

    List-2

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

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

    List-3

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;
	}
...

List-2, after getting the FeignContext, calls the feign method to get Encoder, Decoder, Contract from the FeignContext. In fact, the inner part is obtained from the spring container and Feign.Builder.

We can implement the Request Interceptor interface and then give it to the Spring container. feign automatically adds this interceptor. This implementation is also in FeignClientFactoryBean. In the configureUsingConfiguration method, List-4 follows

    List-4

Map<String, RequestInterceptor> requestInterceptors = context
.getInstances(this.contextId, RequestInterceptor.class);
if (requestInterceptors != null) {
    builder.requestInterceptors(requestInterceptors.values());
}

How is the context.getInstances() method in List-4 implemented internally? Let's look at the parent NamedContextFactory of FeignContext. The setConfigurations method in List-5 is called in List-3 and when the FeignContext is constructed.

    List-5

public void setConfigurations(List<C> configurations) {
    for (C client : configurations) {
        this.configurations.put(client.getName(), client);
    }
}

List-6

  1. The getConext method gets the ApplicationContext, and then gets the bean from the ApplicationContext.
  2. In the getConext method, if the ApplicationContext corresponding to the name does not exist, the createContext method is called to create it.
  3. The AnnotationConfigApplicationContext.register method registers the configuration class into the ApplicationContext and sets the parent of the AnnotationConfigApplicationContext as the current Spring context, so that when the bean is not available in the AnnotationConfigApplicationContext, it is retrieved from the parent ApplicationContext.

    List-6

public <T> T getInstance(String name, Class<T> type) {
    AnnotationConfigApplicationContext context = getContext(name);
    if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
            type).length > 0) {
        return context.getBean(type);
    }
    return null;
}

protected AnnotationConfigApplicationContext getContext(String name) {
    if (!this.contexts.containsKey(name)) {
        synchronized (this.contexts) {
            if (!this.contexts.containsKey(name)) {
                this.contexts.put(name, createContext(name));
            }
        }
    }
    return this.contexts.get(name);
}

protected AnnotationConfigApplicationContext createContext(String name) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    if (this.configurations.containsKey(name)) {
        for (Class<?> configuration : this.configurations.get(name)
                .getConfiguration()) {
            context.register(configuration);
        }
    }
    for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
        if (entry.getKey().startsWith("default.")) {
            for (Class<?> configuration : entry.getValue().getConfiguration()) {
                context.register(configuration);
            }
        }
    }
    context.register(PropertyPlaceholderAutoConfiguration.class,
            this.defaultConfigType);
    context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
            this.propertySourceName,
            Collections.<String, Object>singletonMap(this.propertyName, name)));
    if (this.parent != null) {
        // Uses Environment from parent as well as beans
        context.setParent(this.parent);
        // jdk11 issue
        // https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
        context.setClassLoader(this.parent.getClassLoader());
    }
    context.setDisplayName(generateDisplayName(name));
    context.refresh();
    return context;
}

 

Reference

  1. Source code, github address

Tags: Programming Spring github

Posted on Tue, 08 Oct 2019 06:52:53 -0700 by bradsteele