Spring Coud Feignソース分析
どのようにSpring Cloud Feignを使って声明式の呼び出しを行いますか?
前の文章でSpring Cloud Feignの紹介を書きましたが、どのようにSpring Cloud Feignを使うかについて説明しました.今は簡単に振り返ってみます.1.スタートクラスに@EnbaleFeign Clintsコメントを追加し、Feign Client 2をオープンします.Feign Clientインターフェースを作成して、@Feign Clintコメントを添付して、他のサービス名をリモートに指定します.インターフェースで方法を宣言できます.3.controllerでは、このFeign Clientを通じて呼び出します.
以下は紹介の文章です.Spring Coud Feignの紹介
Spring Coud Feignソース分析:
次はソースコードを分析して、この呼び出しが何を経験したかを見てみます.
スタートクラスに@EnbaleFeign Clientsのコメントを追加した以上、この注釈のソースコードを見てください.
同じ経路ではまた二つの構成が見られます.Feign AutoConfigrationとFeign Clients Configration.これは自動ローディングプロファイルです.Feign Clients ConfigrationではHystrixの配置も見られます.
このルートの下にはFeign Client FactoryBean類があり、このクラスはFactoryBeanインターフェースを実現しました.Feign Clienentに呼出すると、このFeign Client FactoryBenをIOCから読み出し、getObject方法を呼び出す.以下はgetObjectの方法です.
前の文章でSpring Cloud Feignの紹介を書きましたが、どのようにSpring Cloud Feignを使うかについて説明しました.今は簡単に振り返ってみます.1.スタートクラスに@EnbaleFeign Clintsコメントを追加し、Feign Client 2をオープンします.Feign Clientインターフェースを作成して、@Feign Clintコメントを添付して、他のサービス名をリモートに指定します.インターフェースで方法を宣言できます.3.controllerでは、このFeign Clientを通じて呼び出します.
以下は紹介の文章です.Spring Coud Feignの紹介
Spring Coud Feignソース分析:
次はソースコードを分析して、この呼び出しが何を経験したかを見てみます.
スタートクラスに@EnbaleFeign Clientsのコメントを追加した以上、この注釈のソースコードを見てください.
/**
* Scans for interfaces that declare they are feign clients (via {@link FeignClient
* @FeignClient
}). Configures component scanning directives for use with
* {@link org.springframework.context.annotation.Configuration
* @Configuration
} classes.
*
* @author Spencer Gibb
* @author Dave Syer
* @since 1.0
*/
@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 {};
}
この注釈の上の注釈から分かるように、この注釈はFeign Cient、つまり@Feign Clientのインターフェースをスキャンします.EnbaleFeign Clientsの注釈には@Importがあります.Feign Clienents Registar.classを導入しました.Feign Clients Registarというクラスを紹介しました.このクラスの名前から見て、Feign Client登録のクラスです.このクラスに入ってコードを見てください.このクラスはImportBeanDefinitionRegistar、Resource Loader Aware、BeanClass Loader Awareの三つのインターフェースを実現し、インターフェース内の方法を実現しました.ImportBeanDefinitionRegistarインターフェースの中にregister BeanDefinitions方法があります.Feign Clients Registarで実現しました.以下のコードを見ます.@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
この方法では、レギターDefault ConfigrationとレギターFeign Clienntsの二つの方法が相次いで呼び出されていることがわかる.private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
Map 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"));
}
}
この方法は主にEnbaleFeign Clientsラベルに配置されている情報をスキャンして登録します.public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set basePackages;
Map 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 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 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 attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
register Feign Cliennts方法では、@Feign Client注釈修飾のインターフェースをスキャンし、インターフェース情報と注釈の情報をBenDefinitionBuiderの中に割り当て、IOC容器に注入する.同じ経路ではまた二つの構成が見られます.Feign AutoConfigrationとFeign Clients Configration.これは自動ローディングプロファイルです.Feign Clients ConfigrationではHystrixの配置も見られます.
このルートの下にはFeign Client FactoryBean類があり、このクラスはFactoryBeanインターフェースを実現しました.Feign Clienentに呼出すると、このFeign Client FactoryBenをIOCから読み出し、getObject方法を呼び出す.以下はgetObjectの方法です.
@Override
public Object getObject() throws Exception {
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
String url;
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
return loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
return targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}
この方法はまずコンテキストからFeignContectオブジェクトを取得し、Feign Conteetオブジェクトを介してFeign.Buiderを構築する.次は二つの状況に分けて処理します.一つは@Feign Clientがurlを配置していない時、負荷バランスを統合します.一つは@Feign Client配置urlの時、最後のtarget方法に入ったら、方法はFeign.Burderのtarget方法を呼びます.public T target(Target target) {
return build().newInstance(target);
}
public Feign build() {
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
}
ターゲットメソッドでは、ReflectveFeignのnewInstanceメソッドを呼び出します.次にこの方法を見に行きます./**
* creates an api binding to the {@code target}. As this invokes reflection, care should be taken
* to cache the result.
*/
@SuppressWarnings("unchecked")
@Override
public T newInstance(Target target) {
Map nameToHandler = targetToHandlersByName.apply(target);
Map methodToHandler = new LinkedHashMap();
List defaultMethodHandlers = new LinkedList();
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;
}
newInstance方法では、最初からtarget ToHandlersByName.applyを呼び出します.方法は、Map
フォーマットのオブジェクトを返します.この方法に入ってみてください. public Map<String, MethodHandler> apply(Target key) {
List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
for (MethodMetadata md : metadata) {
BuildTemplateByResolvingArgs buildTemplate;
if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder);
} else if (md.bodyIndex() != null) {
buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder);
} else {
buildTemplate = new BuildTemplateByResolvingArgs(md);
}
result.put(md.configKey(),
factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
}
return result;
}
}
この方法はresultを返します.keyはMethodMetadataのconfigKeyです.valueはfactory.creat(key、md、buildTemplate、options、decodededededededededer、error decodededededededededededededededededededededededededededededededededededededededededededededededededededededepublic MethodHandler create(Target> target, MethodMetadata md,
RequestTemplate.Factory buildTemplateFromArgs,
Options options, Decoder decoder, ErrorDecoder errorDecoder) {
return new SynchronousMethodHandler(target, client, retryer, requestInterceptors, logger,
logLevel, md, buildTemplateFromArgs, options, decoder,
errorDecoder, decode404);
}
この方法は直接的にSynch ronousMethodhandlerオブジェクトを新規作成しました.Synch ronousMethodhandlerオブジェクトの中にinvoke方法があります.@Override
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template);
} catch (RetryableException e) {
retryer.continueOrPropagate(e);
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
Synch ronousMethodhandler類はブロック処理を行い、実際にこのinvoke方法を実行し、パラメータによってRequest Templateオブジェクトを生成し、その後execute AndDecode方法を呼び出し、Request Templateを通じてrequestを構築し、http client呼び出しを通じてResonseを取得する.