SpringCloud之Feign组件的整合过程

我的应用程序配置如下:

spring:
  application:
    name: eshop-microservice-auth
server:
  port: 8002
eureka:
  client:
    service-url:
      defautlZone: http://127.0.0.1:8761/eureka,http://127.0.0.1:8762/eureka
  instance:
    prefer-ip-address: false
feign:
  client:
    config:
      eshop-microservice-user:
        connect-timeout: 2000
        read-timeout: 2000
        requestInterceptors:
          - eshop.microservice.auth.configuration.AuthRequestInterceptor

  

我们先从入口注解@EnableFeignClients开始:

@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
}

 可以看到这里导入了FeignClientsRegistrar这个组件,这个组件实现了ImportBeanDefinitionRegistrar接口,所以可以实现Bean的动态注入,主要通过如下方法:

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
//这里注入FeignClientSpecification类型,name=default.eshop.microservice.auth.AuthApplication.FeignClientSpecification的配置
//获取的是EnableFeignClients注解上的defaultConfiguration属性设置的配置信息 registerDefaultConfiguration(metadata, registry);
//通过扫描启动目录下所有的包含有FeignClient注解的类,然后注入 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);


		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);
//这里通过获取注解上configuration属性,注入name=eshop-microservice-user.FeignClientSpecification的配置
//获取的是FeignClient注解上的configuration属性设置的配置信息 registerClientConfiguration(registry, name, attributes.get("configuration")); //注入单个带有FeignClient注解的类,beanClass=FeignClientFactoryBean registerFeignClient(registry, annotationMetadata, attributes); } } } }

 然后进入到registerClientConfiguration方法:

	private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
			Object configuration) {
		BeanDefinitionBuilder builder = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientSpecification.class);
//注入name,configuration两个构造函数 builder.addConstructorArgValue(name); builder.addConstructorArgValue(configuration); registry.registerBeanDefinition( name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition()); }

  

这里自定义了创建Bean的工厂方法,在创建Bean时会调用FeignClientFactoryBean的getObject方法

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

  Feign通过getTarget()正式开始Feign代理对象的创建之路,现在看getTarget中的fegin方法:

	protected Feign.Builder feign(FeignContext context) {
		FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
		Logger logger = loggerFactory.create(this.type);

		// @formatter:off
		Feign.Builder builder = get(context, Feign.Builder.class)
				// required values
				.logger(logger)
				.encoder(get(context, Encoder.class))
				.decoder(get(context, Decoder.class))
				.contract(get(context, Contract.class));
		// @formatter:on

		configureFeign(context, builder);

		return builder;
	}

 这里的get方法:

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

	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();
//configurations前面注入的是registerClientConfiguration方法注入的所有FeignClientSpecification的bean的集合
//读取key匹配的配置,来自FeignClient注解上的configuration if (this.configurations.containsKey(name)) { for (Class<?> configuration : this.configurations.get(name) .getConfiguration()) { context.register(configuration); } }
//读取default.匹配的配置,这个就是前面注入的default.eshop.microservice.auth.AuthApplication.FeignClientSpecification的配置
//来自EnableFeignClients注解上的defaultConfiguration 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; }

  最后会创建AnnotationConfigApplicationContext容器,每一行Feign代理对象都会创建一个容器,这里在createContext时有个this.defaultConfigType属性就是容器的默认配置FeignClientsConfiguration,我们可以从如下代码得到

public class FeignContext extends NamedContextFactory<FeignClientSpecification> {

	public FeignContext() {
		super(FeignClientsConfiguration.class, "feign", "feign.client.name");
	}

}

  现在我们来看FeignClientsConfiguration,这里面配置了Decoder、Encoder、Contract、Retryer,每个配置上都有ConditionalOnMissingBean注解,并且上面还有设置父容器context.setParent(this.parent),如果想覆盖配置只需全局注册这些Bean就行。

现在我们继续看FeignClientFactoryBean的configureFeign方法:

	protected void configureFeign(FeignContext context, Feign.Builder builder) {
		FeignClientProperties properties = this.applicationContext
				.getBean(FeignClientProperties.class);
		if (properties != null) {
			if (properties.isDefaultToProperties()) {
//从配置的容器中设置配置信息 configureUsingConfiguration(context, builder);
//从FeignClientProperties获取默认的配置 configureUsingProperties( properties.getConfig().get(properties.getDefaultConfig()), builder);
//从FeignClientProperties获取该contextId对应的配置,这里有eshop-microservice-user的配置 configureUsingProperties(properties.getConfig().get(this.contextId), builder); } else { configureUsingProperties( properties.getConfig().get(properties.getDefaultConfig()), builder); configureUsingProperties(properties.getConfig().get(this.contextId), builder); configureUsingConfiguration(context, builder); } } else { configureUsingConfiguration(context, builder); } }

 

  然后我们看loadBalance方法,通过该方法去创建Feign的代理类:

	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?");
	}

 这里Client client = getOptional(context, Client.class);获取的Client类型是DefaultFeignLoadBalancedConfiguration类配置的LoadBalancerFeignClient。

然后我们看创建Client的三个参数:

	@Bean
	@ConditionalOnMissingBean
	public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
			SpringClientFactory clientFactory) {
		return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
				clientFactory);
	}

Client.Default:他使用的Http客户端是Feign默认的基于java自带的HttpURLConnection实现,如果想换成ApacheHttpClient或者OkHttpClient,只需引入相应依赖并且设置feign.httpclient.enabled或feign.okhttp.enabled为true就可以了;

cachingFactory:创建Feign负载均衡器工厂;

clientFactory:这个在分析Ribbon的时候接触很多,SpringClientFactory就是Ribbon客户端的容器工厂,每个Ribbon客户端有对应的容器。

然后我们继续回到loadBalance方法中的targeter.target方法,这里targeter默认使用的是HystrixTargeter:

	@Override
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
			FeignContext context, Target.HardCodedTarget<T> target) {
//这里feign的类型是Feign.Builder,如果使用HystrixFeign需要配置feign.hystrix.enabled=true if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) { return feign.target(target); } ... return feign.target(target); }

 接下来我们进入到feign.target后主要看如下方法:

 public <T> T newInstance(Target<T> target) {
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if (Util.isDefault(method)) {
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    InvocationHandler handler = factory.create(target, methodToHandler);
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }

  这里就是把Feign接口生成Feign代理类的方法。

 

posted @ 2019-10-19 18:25  myTang  阅读(892)  评论(0)    收藏  举报