简介:

Feign是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。 Feign被广泛应用在Spring Cloud 的解决方案中,是学习基于Spring Cloud 微服务架构不可或缺的重要组件。

源码分析:

当我们需要在项目中使用Feign的功能,需要在我们的pom.xml文件中引入Maven的依赖,依赖的代码如下:

org.springframework.cloud

Spring-cloud-starter-openfeign

2.2.1.RELEASE

当我们引入这个依赖以后会间接依赖如下的jar包:

要使用Feign的功能,需要在我们的启动类上添加@EnableFeignClients注解,这个注解里面的内容是什么那?

package org.springframework.cloud.openfeign;

import java.lang.Annotation.Documented;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

Import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

import org.springframework.context.annotation.Import;

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.TYPE)

@Documented

@Import(FeignClientsRegistrar.class)

public @interface EnableFeignClients {

//basePackages 属性的别名,允许使用更简洁的注释声明

String[] value() default {};

//扫描包下带注释的组件

String[] basePackages() default {};

//basePackages() 的类型安全的替代方法,用于指定要扫描带注释的组件的软件包,指定类别的包装将被扫描。

Class[] basePackageClasses() default {};

//适用于所有自定义@Configuration,可以包含组成客户端的部分的@Bean

Class[] defaultConfiguration() default {};

Class[] clients() default {};

}

可以看出这个注解类是 spring-cloud-openfeign-core包下的,正是我们前面间接分析的依赖的jar包,这个注解类里面@Import一个FeignClientsRegistrar类,它实现了ImportBeanDefinitionRegistrar接口,SpringBoot在启动时候会调用registerBeanDefinitions方法,该方法的代码如下:

@Override

public void registerBeanDefinitions(AnnotationMetadata metadata,

BeanDefinitionRegistry registry) {

registerDefaultConfiguration(metadata, registry);

registerFeignClients(metadata, registry);

}

首先调用 registerDefaultConfiguration方法,该方法主要判断注解EnableFeignClients是否有defaultConfiguration属性,往容器注入一个类型是FeignClientSpecification,它的构造函数名称分别是String类型的名称、defaultConfiguration类型是Class类型的数组,这个方法主要也是我们自定义的一些加载Bean,如果没有就会使用默认的Bean配置。

在我这个例子中没有自定义defaultConfiguration属性,所以会跳过这个方法的执行,我们重点分析registerFeignClients方法,它是Feign的核心方法,

方法负责读取@EnableFeignClients的属性,获取需要扫描的包名,然后扫描指定的所有包名下的被@FeignClient注解注释的接口,将扫描出来的接口调用registerFeignClient方法注册到spring容器,我们先看registerFeignClients方法的内容:

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

}

}

}

}

首先创建了一个 ClassPathScanningCandidateComponentProvider对象,它主要用于在类路径上扫描指定包(或包含特定注解)的类,并将它们注册为 Spring 容器中的 Bean 候选者。获取启动类下的EnableFeignClients注解信息,创建一个AnnotationTypeFilter的用于匹配FeignClient注解的类,这个FeignClient它的代码如下:

/*

* Copyright 2013-2019 the original author or authors.

*

* Licensed under the Apache License, Version 2.0 (the "License");

* you may not use this file except in compliance with the License.

* You may obtain a copy of the License at

*

* https://www.apache.org/licenses/LICENSE-2.0

*

* Unless required by applicable law or agreed to in writing, software

* distributed under the License is distributed on an "AS IS" BASIS,

* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

* See the License for the specific language governing permissions and

* limitations under the License.

*/

package org.springframework.cloud.openfeign;

import java.lang.annotation.Documented;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

import org.springframework.core.annotation.AliasFor;

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface FeignClient {

//指定要调用的目标服务的名称。该属性通常用于指定服务的名称。

String value() default "";

String serviceId() default "";

//在使用多个 @FeignClient 注解时,用于标识不同的 Feign 客户端,防止冲突。

String contextId() default "";

@AliasFor("value")

String name() default "";

String qualifier() default "";

//指定目标服务的 URL 地址。当不使用服务注册中心时,可以使用该属性直接指定服务的地址。

String url() default "";

//默认情况下,Feign 不会解码 HTTP 404 错误。设置为 true 后,Feign 将解码 404 错误。

boolean decode404() default false;

//指定 Feign 客户端的配置类,该配置类可以包含一些自定义的配置,如超时时间、日志级别等。

Class[] configuration() default {};

//定 Feign 客户端的熔断器(Hystrix)回退类。当调用远程服务失败时,会执行回退类中的逻辑。

Class fallback() default void.class;

//与 fallback 类似,不同之处在于 fallbackFactory 允许提供一个工厂类,用于创建回退类的实例。

Class fallbackFactory() default void.class;

//指定 Feign 客户端的统一前缀路径,该路径会添加到每个请求的 URL 前面。

String path() default "";

boolean primary() default true;

}

把它添加到 ClassPathScanningCandidateComponentProvider对象的成员属性includeFilters上,获取当前要扫描FeignClient注解的包名,如果指定了包名则用指定的,如果没有指定就用启动类所在包路径下,循环变量这个包下的所有子包看是否 又@FeignClient的注解,如果有获取标有该注解的所有属性封装为Map。

根据Map获取@Feign注解的value值,又调用了 registerClientConfiguration方法判断是否有自己定义的Bean,接着调用registerFeignClient方法,这是一个重载方法,该方法的定义如下:

private void registerFeignClient(BeanDefinitionRegistry registry,

AnnotationMetadata annotationMetadata, Map attributes) {

String className = annotationMetadata.getClassName();

BeanDefinitionBuilder definition = BeanDefinitionBuilder

.genericBeanDefinition(FeignClientFactoryBean.class);

validate(attributes);

definition.addPropertyValue("url", getUrl(attributes));

definition.addPropertyValue("path", getPath(attributes));

String name = getName(attributes);

definition.addPropertyValue("name", name);

String contextId = getContextId(attributes);

definition.addPropertyValue("contextId", contextId);

definition.addPropertyValue("type", className);

definition.addPropertyValue("decode404", attributes.get("decode404"));

definition.addPropertyValue("fallback", attributes.get("fallback"));

definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));

definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

String alias = contextId + "FeignClient";

AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be

// null

beanDefinition.setPrimary(primary);

String qualifier = getQualifier(attributes);

if (StringUtils.hasText(qualifier)) {

alias = qualifier;

}

BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,

new String[] { alias });

BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);

}

该方法的逻辑是获取拥有@FeignClient注解的类名,创建一个Bean定义的BeanDefinitionBuilder对象,该对象主要用于创建FeignClientFactoryBean的Bean定义对象,这个类是关键的类,后面会分析它,从Map数据结构中获取url、path、name、contextId、type、decode404、fallback、fallbackFactory属性设置到BeanDefinitionBuilder对象的值上,以后这些属性都会是FeignClientFactoryBean的成员属性。

最后调用BeanDefinitionBuilder对象的getBeanDefinition方法获取FeignClientFactoryBean的Bean定义对象注入到容器中。

到此为止被@Feign修饰的类已经加载到了Spring容器中,此时它的Bean类型已经变为FeignClientFactoryBean类型,但是此时的Bean尚未进行实例化,下一篇文章我主要分析它的实例化过程。

总结:  

本节主要分析了Feign是怎么被加载到Spring容器中的,其实主要就是在我们的启动类中添加@EnableFeignClients注解,然后注解又引入了一个FeignClientsRegistrar,它是Spring容器会回调的一个类,它是去指定的包路径下寻找所有子包标有@FeginClient注解的类并把他们封装为FeignClientFactoryBean类型的Bean加载到spring容器中。好了,我是程序员老徐,如果喜欢我的文章,请点赞关注。

相关阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: