前面已经学习了Ribbon,从Eureka获取服务的实例在通过RestTemplate调用,并转换成需要的对象
如:List
getBody();
可以发现所有的数据调用和转换都是由用户直接来完成的,我们可能不想直接访问Rest接口,如果转换回来的直接是对象而不需要直接使用RestTemplate进行转换就好了,这个时候就需要使用Feign了。
代码Git地址:https://gitee.com/hankin_chj/springcloud-micro-service.git
注意:Feign不建议大家使用,流程简单并发不高的方法可以用一用。
复制springcloud-micro-consumer成一个新的模块springcloud-micro-consumer-feign
Feign服务修改pom文件,增加对feign的支持:
<dependencies>
<dependency>
<groupId>com.chjgroupId>
<artifactId>springcloud-micro-apiartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
dependencies>
注意:这里又有版本问题,如果是Edgware或之前的版本,使用下面的版本
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-feignartifactId>
dependency>
feign的操作其实需要ribbon的支持。
2.1、这个模块专门定义客户端的调用接口,修改pom文件如下:
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>com.chjgroupId>
<artifactId>springcloud-micro-apiartifactId>
dependency>
dependencies>
2.2、安全服务提供方的认证问题
service-feign模块如果要通过Feign进行远程调用,依然需要安全服务提供方的认证问题,不过在feign里面已经集成了这块功能,代码如下:
@Configuration
public class FeignClientConfig {
@Bean
public BasicAuthRequestInterceptor getBasicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("admin", "admin");
}
}
2.3、service-feign模块新建一个IProductClientService接口
@FeignClient(name = "SPRINGCLOUD-MICRO-PRODUCT", configuration = FeignClientConfig.class)
public interface IProductClientService {
@RequestMapping("/prodcut/get/{id}")
public Product getProduct(@PathVariable("id")long id);
@RequestMapping("/prodcut/list")
public List
@RequestMapping("/prodcut/add")
public boolean addPorduct(Product product) ;
}
3.1、consumer-feign引入service-feign包
springcloud-micro-consumer-feign模块修改pom文件,引入springcloud-micro-service-feign包:
<dependency>
<groupId>com.chjgroupId>
<artifactId>springcloud-micro-service-feignartifactId>
dependency>
3.2、consumer-feign模块删除RestConfig.java类
springcloud-micro-consumer-feign模块由于microcloud-service里面已经做了安全验证,并且后面并不直接使用RestTemplate ,所以删除RestConfig.java类。
3.3、consumer-feign模块修改ConsumerProductController
springcloud-micro-consumer-feign模块修改ConsumerProductController,这个时候直接使用microcloud-service定义的服务就可以了,具体实现如下:
@RestController
@RequestMapping("/consumer")
public class ConsumerProductController {
@Resource
private IProductClientService iProductClientService;
@RequestMapping("/product/get")
public Object getProduct(long id) {
return iProductClientService.getProduct(id);
}
@RequestMapping("/product/list")
public Object listProduct() {
return iProductClientService.listProduct();
}
@RequestMapping("/product/add")
public Object addPorduct(Product product) {
return iProductClientService.addPorduct(product);
}
}
由此可见,这个时候ConsumerProductController的代码已经简洁了不少。
3.4、consumer-feign模块修改程序主类
由于使用了feign接口功能,需要添加@EnableFeignClients()注解:
@SpringBootApplication
@EnableEurekaClient
//@RibbonClient(name ="SPRINGCLOUD-MICRO-PRODUCT" ,configuration = RibbonConfig.class)
@EnableFeignClients("com.chj.service") // 扫描feign接口服务的service包
public class ConsumerFeignApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerFeignApp.class,args);
}
}
访问地址:http://localhost/consumer/product/list
可以发现Feign在调用接口的时候自带负载均衡,并且使用了默认的轮询方式,这也不奇怪,因为Fegin里面内置就使用了Ribbon,可以做个测试,看下是否真的如此consumer-feign修改程序主类ConsumerFeignApp。
添加注解:@RibbonClient(name ="MICROCLOUD-PROVIDER-PRODUCT" ,configuration = RibbonConfig.class)
启动测试:http://localhost/consumer/product/list
可以发现,现在的路由规则以及变成了随机访问
前面我们已经知道Feign之中最核心的作用就是将Rest服务的信息转化为接口,这其中还有其他的一些地方应该要考虑,比如,数据的压缩。Rest协议更多的传输的是文本,JSON或者XML,如果用户发送的请求很大,这个时候有必要对数据进行压缩处理,好在feign本身就提供了压缩的支持。
FeignContentGzipEncodingAutoConfiguration可以先看下这个类:
@Configuration
@EnableConfigurationProperties({FeignClientEncodingProperties.class})
@ConditionalOnClass({Feign.class})
@ConditionalOnBean({Client.class})
@ConditionalOnMissingBean(
type = { "okhttp3.OkHttpClient"}
)
@ConditionalOnProperty(
value = { "feign.compression.request.enabled"},
matchIfMissing = false
)
@AutoConfigureAfter({FeignAutoConfiguration.class})
public class FeignContentGzipEncodingAutoConfiguration {
虽然Feign支持压缩,但默认是不开启的,再看下FeignClientEncodingProperties,可以根据这里面的属性进行相关压缩的配置。
@ConfigurationProperties("feign.compression.request")
public class FeignClientEncodingProperties {
private String[] mimeTypes = new String[]{ "text/xml", "application/xml", "application/json"};
private int minRequestSize = 2048;
consumer-feign模块修改application.yml配置文件:
feign:
compression:
request:
enabled: true
mime-types: # 可以被压缩的类型
- text/xml
- application/xml
- application/json
min-request-size: 2048 # 超过2048的字节进行压缩
在构建@FeignClient注解修饰的服务客户端时,会为一个客户端都创建一个feign.Logger实例,可以利用日志来分析Feign的请求细节,不过默认Feign的日志是不开启的。
2.1、consumer-feign模块修改
修改application.yml配置文件,增加日志信息:
logging:
level:
com.chj.service: DEBUG
只添加上面配置还无法实现对DEBUG日志的输出,以因为Feign客户端默认的logger.level对象定义为none级别,所以不会记录feign调用过程中的信息。
public static enum Level {
NONE, // 默认
BASIC,
HEADERS,
FULL;
private Level() {
}
}
2.2、service-feign模块修改FeignClientConfig,开启日志输出
@Configuration
public class FeignClientConfig {
@Bean
public BasicAuthRequestInterceptor getBasicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("admin", "admin");
}
@Bean
public Logger.Level getFeignLoggerLevel() {
return feign.Logger.Level.FULL ;
}
}
2.3、启动访问,查看日志输出
访问诶之:localhost/consumer/product/list
日志打印结果:
2019-11-21 01:41:46.696 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService : [IProductClientService#listProduct] ---> GET http://SPRINGCLOUD-MICRO-PRODUCT/prodcut/list HTTP/1.1
2019-11-21 01:41:46.697 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService : [IProductClientService#listProduct] Authorization: Basic YWRtaW46YWRtaW4=
2019-11-21 01:41:46.697 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService : [IProductClientService#listProduct] ---> END HTTP (0-byte body)
2019-11-21 01:41:46.782 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService : [IProductClientService#listProduct] <--- HTTP/1.1 200 (85ms)
2019-11-21 01:41:46.783 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService : [IProductClientService#listProduct] cache-control: no-cache, no-store, max-age=0, must-revalidate
2019-11-21 01:41:46.783 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService : [IProductClientService#listProduct] content-type: application/json;charset=UTF-8
2019-11-21 01:41:46.783 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService : [IProductClientService#listProduct] date: Wed, 20 Nov 2019 17:41:46 GMT
2019-11-21 01:41:46.783 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService : [IProductClientService#listProduct] expires: 0
2019-11-21 01:41:46.783 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService : [IProductClientService#listProduct] pragma: no-cache
2019-11-21 01:41:46.783 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService : [IProductClientService#listProduct] transfer-encoding: chunked
2019-11-21 01:41:46.783 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService : [IProductClientService#listProduct] x-content-type-options: nosniff
2019-11-21 01:41:46.783 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService : [IProductClientService#listProduct] x-frame-options: DENY
2019-11-21 01:41:46.783 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService : [IProductClientService#listProduct] x-xss-protection: 1; mode=block
2019-11-21 01:41:46.783 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService : [IProductClientService#listProduct]
2019-11-21 01:41:46.783 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService : [IProductClientService#listProduct] [{"productId":null,"productName":"java编程
","productDesc":"springcloud"},{"productId":null,"productName":"Springboot","productDesc":"springcloud"},{"productId":null,"productName":"西游记","productDesc":"springcloud"},{"productId":null,"productName":"水浒传
","productDesc":"springcloud"},{"productId":null,"productName":"西厢记","productDesc":"springcloud"}]
2019-11-21 01:41:46.783 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService : [IProductClientService#listProduct] <--- END HTTP (368-byte body)
·当使用Feign要通过接口的方法访问Rest服务的时候会根据设置的服务类型发出请求,这个请求是发送给Eureka。
·随后由于配置了授权处理,所以继续发送授权信息(Authorization),其实在外面使用RestTemplate的时候也是这么做的,可以对应日志的加密内容和直接访问其实是一样的。
·在进行服务调用的时候Feign融合了Ribbon技术,所以也支持有负载均衡的处理。
Feign = RestTempate + HttpHeader + Ribbon + Eureka 综合体,使用feign大大增加了代码的灵活程度。
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name ="SPRINGCLOUD-MICRO-PRODUCT" ,configuration = RibbonConfig.class)
@EnableFeignClients("com.chj.service") // 扫描feign接口服务的service包
public class ConsumerFeignApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerFeignApp.class,args);
}
}
在@EnableFeignClients标签中,import了一个FeignClientsRegistrar类:
@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 {};
}
org.springframework.cloud.openfeign.FeignClientsRegistrar#registerBeanDefinitions方法里面有两个核心方法,它会在AbstractApplicationContext#refresh()方法中被调用,这部分可以参考spring的源代码。
核心方法registerDefaultConfiguration():从EnableFeignClients的属性值来构建Feign的Configuration。
核心方法registerFeignClients():扫描package,注册被@FeignClient修饰的接口类Bean的信息。
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//TODO 从EnableFeignClients的属性值来构建Feign的Configuration
registerDefaultConfiguration(metadata, registry);
//TODO 扫描package,注册被@FeignClient修饰的接口类Bean的信息
registerFeignClients(metadata, registry);
}
org.springframework.cloud.openfeign.FeignClientsRegistrar#registerDefaultConfiguration
private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//TODO 获取到metadata中关于EnableFeignClients的属性值键值对。
Map
//TODO 如果配置了defaultConfiguration 进行配置,如果没有使用默认的configuration
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}else {
name = "default." + metadata.getClassName();
}
//TODO 进行注册
registerClientConfiguration(registry, name,defaultAttrs.get("defaultConfiguration"));
}
}
2.1、registerClientConfiguration进行注册方法分析
org.springframework.cloud.openfeign.FeignClientsRegistrar#registerClientConfiguration接下来进行注册:
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,Object configuration) {
//使用BeanDefinitionBuilder来生成BeanDefinition,并把它进行注册
BeanDefinitionBuilder builder = BeanDefinitionBuilder .genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition( name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
方法的入参BeanDefinitionRegistry是spring框架用于动态注册BeanDefinition信息的接口,调用registerBeanDefinition方法可以将BeanDefinition注册到Spring容器中,其中name属性就是注册的BeanDefinition的名称,在这里它注册了一个FeignClientSpecification的对象。
FeignClientSpecification实现了NamedContextFactory.Specification接口,它是Feign实例化的重要一环,在上面的方法中,它持有自定义配置的组件实例,SpringCloud使用NamedContextFactory创建一些列的运行上下文ApplicationContext来让对应的Specification在这些上下文中创建实例对象。
2.2、NamedContextFactory有3个功能:
·创建AnnotationConfigApplicationContext上下文。
·在上下文中创建并获取bean实例。
·当上下文销毁时清除其中的feign实例。
NamedContextFactory有个非常重要的子类FeignContext,用于存储各种OpenFeign的组件实例。
public class FeignContext extends NamedContextFactory
public FeignContext() {
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
}
2.3、FeignContext是哪里构建的呢?
配置见:pring-cloud-openfeign-core-2.0.0.RELEASE.jar!\META-INF\spring.factories
2.4、启动配置类FeignAutoConfiguration代码分析:
org.springframework.cloud.openfeign.FeignAutoConfiguration#feignContext方法代码如下:
@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
public class FeignAutoConfiguration {
@Autowired(required = false)
private List
@Bean
public HasFeatures feignFeature() {
return HasFeatures.namedFeature("Feign", Feign.class);
}
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
public class FeignContext extends NamedContextFactory
public FeignContext() {
//将默认的FeignClientsConfiguration作为参数传递给构造函数
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
}
FeignContext创建的时候会将之前FeignClientSpecification通过setConfigurations设置给context上下文。
2.5、NamedContextFactory的createContext方法解析:
代码详见:org.springframework.cloud.context.named.NamedContextFactory#createContext方法。
FeignContext的父类的createContext方法会将创建AnnotationConfigApplicationContext实例,这实例将作为当前上下文的子上下文,用于关联feign组件的不同实例。
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//TODO 获取name所对应的configuration,如果有就注册到子context中
if (this.configurations.containsKey(name)) {
for (Class> configuration : this.configurations.get(name).getConfiguration()) {
context.register(configuration);
}
}
//TODO 注册default的Configuration,也就是 FeignClientsRegistrar类中registerDefaultConfiguration方法中注册的Configuration
for (Map.Entry
if (entry.getKey().startsWith("default.")) {
for (Class> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
//TODO 注册PropertyPlaceholderAutoConfiguration
context.register(PropertyPlaceholderAutoConfiguration.class,this.defaultConfigType);
//TODO 设置Environment的propertySources属性源
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName,
Collections.
if (this.parent != null) { // Uses Environment from parent as well as beans
context.setParent(this.parent);
}
context.setDisplayName(generateDisplayName(name));
context.refresh();
return context;
}
由于NamedContextFactory实现了DisposableBean,所以当实例消亡的时候会调用
@Override
public void destroy() {
Collection
for (AnnotationConfigApplicationContext context : values) {
context.close();
}
this.contexts.clear();
}
2.6、总结一下:
NamedContextFactory会创建出AnnotationConfigApplicationContext实例,并以name作为唯一标识,然后每个AnnotationConfigApplicationContext实例都会注册部分配置类,从而可以给出一系列的基于配置类生成的组件实例,这样就可以基于name来管理一系列的组件实例,为不同的FeignClient准备不同配置组件实例。
3.1、registerFeignClients源码分析
FeignClientsRegistrar做的第二件事就是扫描指定包下的类文件,注册@FeignClient修饰的接口类信息。
org.springframework.cloud.openfeign.FeignClientsRegistrar.registerFeignClients(metadata, registry)源码:
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//TODO 生成自定义的ClassPathScanningCandidateComponentProvider
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set
//TODO 获取EnableFeignClients所有属性的键值对
Map
//TODO 依照AnnotationTypeFilter 来进行过滤,只会扫描出EnableFeignClients修饰的类
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
final Class>[] clients = attrs == null ? null: (Class>[]) attrs.get("clients");
//TODO 如果没有配置clients属性,那么就要扫描basePackages
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
} else {
final Set
basePackages = new HashSet<>();
//TODO 遍历上诉过程中获取basePackages 的列表
for (String basePackage : basePackages) {
//TODO 获取basePackage中所有的BeanDefinition
Set
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");
//TODO 从这些BeanDefinition中获取FeignClient的属性值
Map
FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
//TODO 对单独的某个FeignClient的configuration进行配置
registerClientConfiguration(registry, name,attributes.get("configuration"));
//TODO 注册
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
接下来分析注册方法registerFeignClient()如下:
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map
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);
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 = name + "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);
}
总结:
FeignClientsRegistrar方法会依据@EnableFeignClients的属性获取要扫描的包路径信息,然后获取这些包下被@FeignClient注解修饰的接口的BeanDefinition。例如springcloud-micro-service-feign模块的IProductClientService接口实现:
@FeignClient(name = "SPRINGCLOUD-MICRO-PRODUCT", configuration = FeignClientConfig.class)
public interface IProductClientService {
3.2、实例化@FeignClient修饰的接口
FeignClientFactoryBean是工厂类,Spring容器通过调用它的getObject方法来获取对应的bean实例,被@FeignClient修饰的接口都是通过FeignClientFactoryBean的getObject方法来进行实例化的。
源码见org.springframework.cloud.openfeign.FeignClientFactoryBean#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();
//调用FeignContext的getInstance方法获取Client对象
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
client = ((LoadBalancerFeignClient)client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, new HardCodedTarget<>(this.type, this.name, url));
}
3.3、调用FeignContext的getInstance方法获取Client对象
protected <T> T getOptional(FeignContext context, Class<T> type) {
return context.getInstance(this.name, type);
}
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;
}
3.4、DefaultTargeter的实现
Targeter是一个接口,它的target方法会生成对应实例对象,它有两个实现类,分表为DefaultTargeter和HystrixTargeter,fegign使用HystrixTargeter这一层抽象来封装关于Hystrix的实现,DefaultTargeter的实现如下所示,只是调用了Feign.Builder的target方法
class DefaultTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target) {
return feign.target(target);
}
}
Feign.Builder负责生成被@FeignClient修饰的FeignClient接口类实例,它通过JAVA反射机制构建:
代码见:feign-core-9.5.1.jar!\feign\Feign.target方法:
public
return this.build().newInstance(target);
}
public Feign build() {
Factory synchronousMethodHandlerFactory = new Factory(this.client, this.retryer,
this.requestInterceptors, this.logger, this.logLevel, this.decode404);
ParseHandlersByName handlersByName = new ParseHandlersByName(this.contract, this.options, this.encoder,
this.decoder, this.errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, this.invocationHandlerFactory);
}
3.5、实例化方法ReflectiveFeign
feign-core-9.5.1.jar!\feign\ReflectiveFeign.newInstance()方法分析:
这方法主要做两件事:
1)扫描FeignClient接口类的所有函数,生成对应的Handler
2)使用Proxy生成FeignClient的实例对象
public <T> T newInstance(Target<T> target) {
//TODO arseHandlersByName.appy方法填充信息
Map
Map
List
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if(Util.isDefault(method)) {
//TODO 为每一个默认方法生成一个DefaultMethodHandler
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
//TODO 生成java InvocationHandler 是个动态代理的类
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;
}
3.6、扫描FeignClient接口类的所有函数,生成对应的Handler
在扫描FeignClient接口类所有函数生成对应的Handle过程中,feign会生成调用该函数时发送网络请求的模板,也就是RequestTemplate实例,RequestTemplate中包含了发送网络请求的URL和产生填充信息,@RequestMapping、@RequestVariable等注解信息也会包含到RequestTemplate中,这一具体过程就是ParseHandlersByName.apply()方法来实现的。
1)feign.ReflectiveFeign.ParseHandlersByName#apply方法分析:
public Map
//获取type中所有的方法信息,会根据注解生成每个方法的RequestTemplate
List
Map
MethodMetadata md;
Object buildTemplate;
for(Iterator var4 = metadata.iterator(); var4.hasNext(); result.put(md.configKey(),
this.factory.create(key, md, (Factory)buildTemplate,
this.options, this.decoder, this.errorDecoder))) {
md = (MethodMetadata)var4.next();
if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
buildTemplate = new ReflectiveFeign.BuildFormEncodedTemplateFromArgs(md, this.encoder);
} else if (md.bodyIndex() != null) {
buildTemplate = new ReflectiveFeign.BuildEncodedTemplateFromArgs(md, this.encoder);
} else {
buildTemplate = new ReflectiveFeign.BuildTemplateByResolvingArgs(md);
}
}
return result;
}
}
Contract的默认实现是org.springframework.cloud.openfeign.support.SpringMvcContract其基类为Contract.BaseContract。
public abstract static class BaseContract implements Contract {
2)parseAndValidateMetadata()方法代码解析
org.springframework.cloud.openfeign.support.SpringMvcContract.parseAndValidateMetadata()方法会与HTTP请求相关的所有函数的基本信息和注解信息
@Override
public MethodMetadata parseAndValidateMetadata(Class> targetType, Method method) {
this.processedMethods.put(Feign.configKey(targetType, method), method);
//调用父类BaseContract的函数
MethodMetadata md = super.parseAndValidateMetadata(targetType, method);
RequestMapping classAnnotation = findMergedAnnotation(targetType, RequestMapping.class);
//处理RequestMapping注解
if (classAnnotation != null) {
if (!md.template().headers().containsKey(ACCEPT)) {
parseProduces(md, method, classAnnotation);
}
if (!md.template().headers().containsKey(CONTENT_TYPE)) {
parseConsumes(md, method, classAnnotation);
}
parseHeaders(md, method, classAnnotation);
}
return md;
}
3)feign.Contract.BaseContract#parseAndValidateMetadata 父类的parseAndValidateMetadata方法会依次解析接口类中的注解,方法中的注解,和各种参数注解,并将这些注解包含的信息封装到MethodMetadata 对象中,然后返回。
protected MethodMetadata parseAndValidateMetadata(Class> targetType, Method method) {
MethodMetadata data = new MethodMetadata();
//TODO 函数的返回值
data.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType()));
data.configKey(Feign.configKey(targetType, method));
//TODO 获取并处理修饰class的注解信息
if(targetType.getInterfaces().length == 1) {
processAnnotationOnClass(data, targetType.getInterfaces()[0]);
}
//TODO 调用子类processAnnotationOnClass的实现
processAnnotationOnClass(data, targetType);
//TODO 处理修饰method的注解信息
for (Annotation methodAnnotation : method.getAnnotations()) {
processAnnotationOnMethod(data, methodAnnotation, method);
}
checkState(data.template().method() != null,
"Method %s not annotated with HTTP method type (ex. GET, POST)",method.getName());
//TODO 方法参数的类型
Class>[] parameterTypes = method.getParameterTypes();
Type[] genericParameterTypes = method.getGenericParameterTypes();
//TODO 方法参数的注解
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
int count = parameterAnnotations.length;
//TODO 依次处理各个函数参数的注解
for (int i = 0; i < count; i++) {
boolean isHttpAnnotation = false;
if (parameterAnnotations[i] != null) {
isHttpAnnotation = processAnnotationsOnParameter(data, parameterAnnotations[i], i);
}
if (parameterTypes[i] == URI.class) {
data.urlIndex(i);
} else if (!isHttpAnnotation) {
checkState(data.formParams().isEmpty(), "Body parameters cannot be used with form parameters.");
checkState(data.bodyIndex() == null, "Method has too many Body parameters: %s", method);
data.bodyIndex(i);
data.bodyType(Types.resolve(targetType, targetType, genericParameterTypes[i]));
}
}
if (data.headerMapIndex() != null) {
checkMapString("HeaderMap", parameterTypes[data.headerMapIndex()], genericParameterTypes[data.headerMapIndex()]);
}
if (data.queryMapIndex() != null) {
checkMapString("QueryMap", parameterTypes[data.queryMapIndex()], genericParameterTypes[data.queryMapIndex()]);
}
return data;
}
3.7、使用Proxy生成FeignClient的实例对象
1)实例化方法分析feign.ReflectiveFeign#newInstance:
InvocationHandler handler = factory.create(target, methodToHandler);中另外一件事情是OpenFeign使用Proxy.newProxyInstance方法来创建FeignClient接口类的实例,然后将InvocationHandle绑定到接口实例上,用于处理接口类函数调用。
feign.InvocationHandlerFactory.Default()方法:
public interface InvocationHandlerFactory {
InvocationHandler create(Target var1, Map
public static final class Default implements InvocationHandlerFactory {
public Default() {
}
public InvocationHandler create(Target target, Map
return new FeignInvocationHandler(target, dispatch);
}
}
public interface MethodHandler {
Object invoke(Object[] var1) throws Throwable;
}
}
Default 实现了InvocationHandlerFactory 接口,create方法其实返回FeignInvocationHandler的实例。
2)ReflectiveFeign.FeignInvocationHandler.invoke()方法源码如下:
static class FeignInvocationHandler implements InvocationHandler {
private final Target target;
private final Map
FeignInvocationHandler(Target target, Map
this.target = (Target)Util.checkNotNull(target, "target", new Object[0]);
this.dispatch = (Map)Util.checkNotNull(dispatch, "dispatch for %s", new Object[]{target});
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (!"equals".equals(method.getName())) {
if ("hashCode".equals(method.getName())) {
return this.hashCode();
} else {
return "toString".equals(method.getName()) ? this.toString() :
((MethodHandler)this.dispatch.get(method)).invoke(args);
}
} else {
try {
Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return this.equals(otherHandler);
} catch (IllegalArgumentException var5) {
return false;
}
}
}
注意:((MethodHandler)this.dispatch.get(method)).invoke(args),Invoke方法会根据函数名称来调用不同的MethodHandle实例invoke方法。
3.8、网络请求
接下来就直接使用FeignClient接口类的实例,调用它的函数来发送网络请求,发送网络请求可以分为3个阶段:
1)是将函数实际参数添加到RequestTemplate中
2)调用Target生成具体的Request对象
3)调用Client来发送网络请求,将Response转化为对象返回
首先分析feign.SynchronousMethodHandler#invoke()方法:
public Object invoke(Object[] argv) throws Throwable {
//根据函数参数创建RequestTemplate
RequestTemplate template = this.buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while(true) {
try {
return this.executeAndDecode(template);
} catch (RetryableException var5) {
retryer.continueOrPropagate(var5);
if (this.logLevel != Level.NONE) {
this.logger.logRetry(this.metadata.configKey(), this.logLevel);
}
}
}
}
3.9、根据函数参数创建RequestTemplate.create()
@Override
public RequestTemplate create(Object[] argv) {
RequestTemplate mutable = new RequestTemplate(metadata.template());
//TODO 设置url
if (metadata.urlIndex() != null) {
int urlIndex = metadata.urlIndex();
checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex);
mutable.insert(0, String.valueOf(argv[urlIndex]));
}
Map
//TODO 遍历MethodMetadata中关于参数的所以以及对应名称的配置信息
for (Entry
int i = entry.getKey();
//TODO entry.getKey就是参数的索引
Object value = argv[entry.getKey()];
if (value != null) { // Null values are skipped.
//TODO indexToExpander保存着将各种类型参数的值转化为String类型的Expander转换器
if (indexToExpander.containsKey(i)) {
//TODO 将Value值为String
value = expandElements(indexToExpander.get(i), value);
}
for (String name : entry.getValue()) {
varBuilder.put(name, value);
}
}
}
RequestTemplate template = resolve(argv, mutable, varBuilder);
//TODO 设置queryMap参数
if (metadata.queryMapIndex() != null) {
template = addQueryMapQueryParameters((Map
}
//TODO 设置headersMap参数
if (metadata.headerMapIndex() != null) {
template = addHeaderMapHeaders((Map
}
return template;
}
3.10、feign.RequestTemplate#resolve()方法代码:
protected RequestTemplate resolve(Object[] argv, RequestTemplate mutable, Map
Map
Iterator var5 = this.metadata.indexToEncoded().entrySet().iterator();
while(var5.hasNext()) {
Entry
Collection
Iterator var8 = names.iterator();
while(var8.hasNext()) {
String name = (String)var8.next();
variableToEncoded.put(name, entry.getValue());
}
}
return mutable.resolve(variables, variableToEncoded);
}
}
回调到feign-core-9.5.1.jar!\feign\RequestTemplate.resolve()方法:
RequestTemplate resolve(Map
//替换query数值,将{queryVariable} 替换成实际的值
this.replaceQueryValues(unencoded, alreadyEncoded);
Map
Iterator var4 = unencoded.entrySet().iterator();
while(var4.hasNext()) {
//把所有参数进行编码
Entry
String key = (String)entry.getKey();
Object objectValue = entry.getValue();
String encodedValue = this.encodeValueIfNotEncoded(key, objectValue, alreadyEncoded);
encoded.put(key, encodedValue);
}
String resolvedUrl = expand(this.url.toString(), encoded).replace("+", "%20");
if (this.decodeSlash) {
resolvedUrl = resolvedUrl.replace("%2F", "/");
}
this.url = new StringBuilder(resolvedUrl);
//把头部信息进行传串行化
Map
Iterator var14 = this.headers.keySet().iterator();
while(var14.hasNext()) {
String field = (String)var14.next();
Collection
Iterator var9 = Util.valuesOrEmpty(this.headers, field).iterator();
while(var9.hasNext()) {
String value = (String)var9.next();
String resolved = expand(value, unencoded);
resolvedValues.add(resolved);
}
resolvedHeaders.put(field, resolvedValues);
}
this.headers.clear();
this.headers.putAll(resolvedHeaders);
if (this.bodyTemplate != null) {
//处理body信息
this.body(urlDecode(expand(this.bodyTemplate, encoded)));
}
return this;
}
feign.SynchronousMethodHandler#invoke方法中的executeAndDecode()方法会根据RequestTemplate生成Request对象,然后交给Client实例发送网络请求,最后返回对应的函数返回类型的实例。
这里的client为org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient
3.11、executeAndDecode()源码分析
源码位置:feign.SynchronousMethodHandler.executeAndDecode()
Object executeAndDecode(RequestTemplate template) throws Throwable {
//TODO 根据RequestTemplate生成Request
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
//TODO client发送网络请求
response = client.execute(request, options);
//TODO ensure the request is set. TODO: remove in Feign 10
response.toBuilder().request(request).build();
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
throw errorExecuting(request, e);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
boolean shouldClose = true;
try {
if (logLevel != Logger.Level.NONE) {
response =logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
// ensure the request is set. TODO: remove in Feign 10
response.toBuilder().request(request).build();
}
//TODO 如果是response返回类型就可以直接返回
if (Response.class == metadata.returnType()) {
if (response.body() == null) {
return response;
}
//TODO 设置body
if (response.body().length() == null || response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
shouldClose = false;
return response;
}
// Ensure the response body is disconnected
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
return response.toBuilder().body(bodyData).build();
}
if (response.status() >= 200 && response.status() < 300) {
if (void.class == metadata.returnType()) {
return null;
} else {
return decode(response);
}
} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
return decode(response);
} else {
throw errorDecoder.decode(metadata.configKey(), response);
}
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
}
throw errorReading(request, response, e);
} finally {
if (shouldClose) {
ensureClosed(response.body());
}
}
}