Процесс сборки Feign и пользовательские расширения

Spring Cloud

spring-cloud-openfeign-core-2.1.1.RELEASE.jarсерединаHystrixFeignПодробный процесс сборки:

@EnableFeignClients -> FeignClientsRegistrar сканирует аннотированные классы @Feign -> FeignClientFactoryBean создает FeignClient через Targeter -> Targeter создает Feign через Feign.Builder -> Feign.Builder

1. Подготовка (конфигурация)

  1. Класс автоматической настройки FeignAutoConfiguration
	@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. Когда класс feign.hystrix.HystrixFeign существует, онHystrixTargeterзарегистрирован какTargeterтип фасоли

  2. Если класс feign.hystrix.HystrixFeign не существует, используйтеDefaultTargeter.

  3. Похоже, что можно использовать настраиваемый таргетинг вместо Hystrix или по умолчанию, чтобы можно было настраивать различные функции.Вообще-то нет,так какTargeterдаpackageУровень доступа.

  4. FeignClientsConfiguration

@Configuration
public class FeignClientsConfiguration {
    
            
    @Bean
    @ConditionalOnMissingBean
    public Retryer feignRetryer() {
        return Retryer.NEVER_RETRY;
    }

    @Bean
    @Scope("prototype")
    @ConditionalOnMissingBean
    public Feign.Builder feignBuilder(Retryer retryer) {
        return Feign.builder().retryer(retryer);
    }
    
	@Configuration
	@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
	protected static class HystrixFeignConfiguration {
		@Bean
		@Scope("prototype")
		@ConditionalOnMissingBean
		@ConditionalOnProperty(name = "feign.hystrix.enabled")
		public Feign.Builder feignHystrixBuilder() {
			return HystrixFeign.builder();
		}
	}
}

важный:Feignи внутренний классFeign.BuilderобаpublicУровень доступа, вы можете вводить пользовательские компоненты.

2. Классы EnableFeignClients и FeignClientsRegistrar

Зарегистрируйте класс, аннотированный с помощью @FeignClient, как компонент Spring и используйте конфигурацию в аннотации.

  1. Импортируйте класс FeignClientsRegistrar в аннотацию @EnableFeignClients.
  2. Класс FeignClientsRegistrar реализует класс ImportBeanDefinitionRegistrar, и метод реализации будет выполняться фреймворком Spring.registerBeanDefinitions(AnnotationMetaData, BeanDefinitionRegistry)
  3. FeignClientsRegistrar вregisterBeanDefinitionsметод вызывает два метода
    1. registerDefaultConfiguration: зарегистрировать конфигурацию по умолчанию
    2. registerFeignClients: Зарегистрировать клиент Feign (фокус)
  4. registerFeignClients:Получать@EnableFeignClientsКонфигурация, указанная в аннотации, сканирует фиктивный клиент.
  5. registerFeignClients:пройти черезregisterFeignClient(BeanDefinitionRegistry, AnnotationMetadata, Map)Метод регистрирует каждый feignClient, процесс:@FeignClientКонфигурация, определенная в аннотации, применяет конфигурацию к фабрике bean-компонентов Spring.FeignClientFactoryBean, через фабричный классFeignClientFactoryBeanза каждое использование@FeignClientПроизводство аннотированного классаFeignClient, подробности см. в следующем разделе

3.FeignClientFactoryBean

Фабричный компонент FeignClient.

class FeignClientFactoryBean
    implements FactoryBean<Object>, InitializingBean, ApplicationContextAware{
    //...
}

методом реализацииFactoryBean#getObject()Для создания FeignClient с помощью фреймворка Spring.

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

/**
 * 获得目标
 * 1. 获得FeignContext
 * 2. 从FeignContext中获得Feign构建器Feign.Builder
 * 3. 从FeignContext中获得Client,判断是否进行负载均衡
 * 4. 从FeignContext中获得Target,并执行Target的默认方法target(FeignClientFactoryBean, Feign.Builder,
			FeignContext, Target.HardCodedTarget<T>);
 * 5.由于一开始注入的Feign.Builder是HystrixFeign.Builder,则此处是调用HystrixFeign.Builder里的对应方法
 */
<T> T getTarget() {
    FeignContext context = this.applicationContext.getBean(FeignContext.class);
    Feign.Builder builder = feign(context);
    //省略部分代码
    // ......
    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));
}

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

Фабрика получает объект (цель):

1. 获得FeignContext(feign上下文)
2. 从FeignContext中获得Feign构建器Feign.Builder(public,可以在此使用自定义构建器)
3. 从FeignContext中获得Client,判断是否进行负载均衡
4. 从FeignContext中获得Target,并执行Target的默认方法target(FeignClientFactoryBean, Feign.Builder,
  FeignContext, Target.HardCodedTarget<T>);
5. 由于一开始注入的 *Targeter* 是 *HystrixTargeter* ,则此处是调用 HystrixTargeter 里的对应方法(从第一节的配置来看,只要 *feign.hystrix.HystrixFeign* 类存在,就是注入的 *HystrixTargeter *, 否则是 *DefaultTargeter*,对于需要**自定义构建feign的,这里不太重要**)

4.Targeter

4.1.HystrixTargeter

class HystrixTargeter implements Targeter {

	@Override
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
			FeignContext context, Target.HardCodedTarget<T> target) {
        // 若不是 HystrixFeign,则执行其对应的默认target方法。
        // 此处只处理HystrixFeign。
		if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
			return feign.target(target);
		}
		feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
		SetterFactory setterFactory = getOptional(factory.getName(), context,
				SetterFactory.class);
		if (setterFactory != null) {
			builder.setterFactory(setterFactory);
		}
		Class<?> fallback = factory.getFallback();
		if (fallback != void.class) {
			return targetWithFallback(factory.getName(), context, target, builder,
					fallback);
		}
		Class<?> fallbackFactory = factory.getFallbackFactory();
		if (fallbackFactory != void.class) {
			return targetWithFallbackFactory(factory.getName(), context, target, builder,
					fallbackFactory);
		}

        // 调用从Feign.Builder继承的方法。
		return feign.target(target);
	}
    
    	private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context,
			Target.HardCodedTarget<T> target, HystrixFeign.Builder builder,
			Class<?> fallbackFactoryClass) {
		FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>) getFromContext(
				"fallbackFactory", feignClientName, context, fallbackFactoryClass,
				FallbackFactory.class);
		return builder.target(target, fallbackFactory);
	}

	private <T> T targetWithFallback(String feignClientName, FeignContext context,
			Target.HardCodedTarget<T> target, HystrixFeign.Builder builder,
			Class<?> fallback) {
		T fallbackInstance = getFromContext("fallback", feignClientName, context,
				fallback, target.type());
		return builder.target(target, fallbackInstance);
	}
    
    //...
}
  1. HystrixTarget обрабатывает толькоFeign.BuilderТипfeign.hystrix.HystrixFeign.Builderиз
  2. Если построитель симуляций не относится к типу feign.hystrix.HystrixFeign.Builder, выполните целевой метод по умолчанию внедренного построителя симуляций.
  3. Таким образом, даже если введенный TargeterHystrixTargeter, вы также можете выполнить пользовательский здесьFeign.Builder.
  4. понимать:Feign.Builder#target(Target)Метод обычно не переопределяется (позже мы объясним, почему этот метод не переопределяется).

4.2.DefaultTargeter

class DefaultTargeter implements Targeter {
	@Override
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
			FeignContext context, Target.HardCodedTarget<T> target) {
		return feign.target(target);
	}
}
  1. Выполняет целевой метод по умолчанию, соответствующий Feign.Builder (под)типу.
  2. понимать:Feign.Builder#target(Target)Метод обычно не переопределяется (позже мы объясним, почему этот метод не переопределяется).

5.FeignBuilder

feign builder: создайте фиктивный объект.

FeignЦель состоит в том, чтобы обернуть http API в спокойном стиле для облегчения разработки.

В реализации Feign — это фиктивный объект, который генерирует фиктивные объекты для целевого http API (Feign#newInstance) фабрика.

Вышеупомянутые шаги в настоящее время требуются через соответствующийBuilderПостройте соответствующий Feign.

public abstract class Feign {

  public static Builder builder() {
    return new Builder();
  }
   
  public abstract <T> T newInstance(Target<T> target);
  
  public static class Builder {
    public <T> T target(Target<T> target) {
      return build().newInstance(target);
    }

    public Feign build() {
      SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
          new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel, decode404, closeAfterDecode, propagationPolicy);
      ParseHandlersByName handlersByName =
          new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
              errorDecoder, synchronousMethodHandlerFactory);
      return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
    }
  }
}

  1. Feign.Builder#target(Target)Метод действительно вызываетbuild()метод для построения объекта, поэтому переопределения метода build() достаточно, нет необходимости также переопределять метод target(Target)
  2. Feignи внутренний классFeign.Builderобаpublic,Может переопределять и вводить пользовательские bean-компоненты.

5.1.HystrixFeign

public final class HystrixFeign {
  public static final class Builder extends Feign.Builder {  
	@Override
    public Feign build() {
      return build(null);
    }
    
    // 提供一系列的target方法,支持各种配置:fallback、FallBackFactory等
    public <T> T target(Target<T> target, T fallback) {
      return build(fallback != null ? new FallbackFactory.Default<T>(fallback) : null)
          .newInstance(target);
    }

    public <T> T target(Target<T> target, FallbackFactory<? extends T> fallbackFactory) {
      return build(fallbackFactory).newInstance(target);
    }
      
    
    public <T> T target(Class<T> apiType, String url, T fallback) {
      return target(new Target.HardCodedTarget<T>(apiType, url), fallback);
    }

    public <T> T target(Class<T> apiType,
                        String url,
                        FallbackFactory<? extends T> fallbackFactory) {
      return target(new Target.HardCodedTarget<T>(apiType, url), fallbackFactory);
    }

    /** Configures components needed for hystrix integration. */
    Feign build(final FallbackFactory<?> nullableFallbackFactory) {
      super.invocationHandlerFactory(new InvocationHandlerFactory() {
        @Override
        public InvocationHandler create(Target target,
                                        Map<Method, MethodHandler> dispatch) {
          return new HystrixInvocationHandler(target, dispatch, setterFactory,
              nullableFallbackFactory);
        }
      });
      super.contract(new HystrixDelegatingContract(contract));
      return super.build();
    }

На этом этапе можно настроить все, что необходимо установить.

  1. Хотя метод сборки включаетInvocationHandler, но в принципе ничего менять не надо, аInvocationHandlerДаже такpackageуровень доступа, поэтому мне пришлось скопировать один и использовать свой.
  2. HystrixDelegatingContract является общедоступным, если вам не нужно его изменять, вы все равно используете его.

5.2 Примеры

Следующий пример ссылкиSentinelFeign
один из нихYiFeiXiInvocationHandlerа такжеYiFeiXiFeignFallbackFactoryобычай.

@Override
public Feign build() {
    super.invocationHandlerFactory(new InvocationHandlerFactory() {
        @Override
        public InvocationHandler create(Target target,
                                        Map<Method, MethodHandler> dispatch) {
            // using reflect get fallback and fallbackFactory properties from
            // FeignClientFactoryBean because FeignClientFactoryBean is a package
            // level class, we can not use it in our package
            Object feignClientFactoryBean = Builder.this.applicationContext
                .getBean("&" + target.type().getName());

            Class fallback = (Class) getFieldValue(feignClientFactoryBean,
                                                   "fallback");
            Class fallbackFactory = (Class) getFieldValue(feignClientFactoryBean,
                                                          "fallbackFactory");
            String name = (String) getFieldValue(feignClientFactoryBean, "name");

            Object fallbackInstance;
            FallbackFactory fallbackFactoryInstance;
            // check fallback and fallbackFactory properties
            // 以下逻辑在HystrixTargeter中有,但执行自定义的builder,不会执行到那段逻辑,因此此处加上。
            if (void.class != fallback) {
                fallbackInstance = getFromContext(name, "fallback", fallback,
                                                  target.type());
                return new YiFeiXiInvocationHandler(target, dispatch, setterFactory,
                                                    new FallbackFactory.Default(fallbackInstance));
            }
            if (void.class != fallbackFactory) {
                fallbackFactoryInstance = (FallbackFactory) getFromContext(name,
                                                                           "fallbackFactory", fallbackFactory,
                                                                           FallbackFactory.class);
                return new YiFeiXiInvocationHandler(target, dispatch, setterFactory,
                                                    fallbackFactoryInstance);
            }
            // 若注解中没有使用fallback或fallbackFactory,则使用一个默认的FallbackFactory。
            return new YiFeiXiInvocationHandler(target, dispatch, setterFactory, new YiFeiXiFeignFallbackFactory<>(target));
        }

        private Object getFromContext(String name, String type,
                                      Class fallbackType, Class targetType) {
            Object fallbackInstance = feignContext.getInstance(name,
                                                               fallbackType);
            if (fallbackInstance == null) {
                throw new IllegalStateException(String.format(
                    "No %s instance of type %s found for feign client %s",
                    type, fallbackType, name));
            }

            if (!targetType.isAssignableFrom(fallbackType)) {
                throw new IllegalStateException(String.format(
                    "Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
                    type, fallbackType, targetType, name));
            }
            return fallbackInstance;
        }
    });

    super.contract(new HystrixDelegatingContract(contract));
    return super.build();
}

Необходимо настроить fallbackFactory, а затем реализоватьfeign.hystrix.FallbackFactoryкласс, необходимо настроить запасной вариант, а затем реализоватьorg.springframework.cglib.proxy.MethodInterceptorПросто

6. Резюме

  1. Поскольку процесс сборки Feign используетTargeterдаpackageУровень доступа, нельзя использовать пользовательский
  2. Feignа такжеFeign.Builderдаpublilc, давая нам возможность расширяться.

7. Ссылки

  1. feign-hystrix-10.1.0.jarа такжеspring-cloud-openfeign-core-2.1.1.RELEASE.jar
  2. spring-cloud-alibaba-sentinel-0.9.0.RELEASE.jarсерединаsentinelFeignвыполнить
  3. Spring Cloud Alibaba Sentinel объединяет дизайн и реализацию Feign