最近把Spring的IOC和AOP以及Sprongboot相关的知识又学习了一遍,在这个基础上再次看了Feign源码感觉有了新的体会,整体思路更加清晰了。这里做一个学习总结
从Feign使用层面来说,主要有两个步骤
1 给启动类上加@EnableFeignClients注解
2 定义一个接口,配置@FeignClient注解,并且给接口中的每个方法都加上mvc注解齐活了。
配置完成spring容器会帮我们为这个接口创建一个代理类,调用代理类的方法即可实现远程调用,就好像调用本地的service一样方便。
要想搞明白feign是如何实现这个能力的,主要需要搞清除两个问题
1 代理类是何时被创建的
2 代理类中如何实现的远程调用
下面就一点一点的找出这两个问题的答案
feign有两个入口,他们相辅相成,其中的一个入口来至springboot自动装配。springboot启动后默认会装配一些东西,其中既有feign相关的
如下:
spring-cloud-openfeign-core-2.2.2.RELEASE.jar包下的spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration,\
org.springframework.cloud.openfeign.FeignAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfiguration
这里自动装配了很多东西。装配的内容会根据各个项目的需要进行调整。以下是我本地项目的装配情况,装配内容也不少这里介绍一些关键的目的是为了承接后边的内容,下面分别看下【FeignAutoConfiguration、FeignRibbonClientAutoConfiguration、DefaultFeignLoadBalancedConfiguration】这三个自动装配类
FeignAutoConfiguration类
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class)
//【FeignClientProperties】feign.client前缀开头的参数 这个对象在另一个入口中会用到
@EnableConfigurationProperties({ FeignClientProperties.class,
FeignHttpClientProperties.class })
@Import(DefaultGzipDecoderConfiguration.class)
public class FeignAutoConfiguration {
//一堆FeignClientSpecification对象 这些对象是在另一个入口注入到容器的 在这里被取出来了
//为了放入FeignContext对象中
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
//容器工厂 该类为每一个客户端(被@FeignClient注释的接口) 创建一个子容器
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
protected static class HystrixFeignTargeterConfiguration {
//该类用于创建代理类用的 另一个入口中会用到
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new HystrixTargeter();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new DefaultTargeter();
}
}
}
FeignRibbonClientAutoConfiguration
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
matchIfMissing = true)
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
// Order is important here, last should be the default, first should be optional
// see
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
OkHttpFeignLoadBalancedConfiguration.class,
//主要看这个类
DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
。。。。略
//这里注入了LoadBalancerFeignClient的默认配置 后续会去容器里取
@Bean
@ConditionalOnMissingBean
public Request.Options feignRequestOptions() {
return LoadBalancerFeignClient.DEFAULT_OPTIONS;
}
}
DefaultFeignLoadBalancedConfiguration
@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {
//这里注册了LoadBalancerFeignClient类到容器 另一个入口需要
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
//记住这第一个参数Client.Default 在最最最后边会用到
return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
clientFactory);
}
}
这里总结下,3个类中向容器注册了不少bean,其中主要有5个bean需要重点关注下
1 FeignClientProperties feign配置对象【对应application.yml中feign.client开头的配置】
2 FeignContext 容器工厂,可以创建容器。内部放了一个FeignClientSpecification集合,集合是在第二个入口注册的
3 HystrixTargeter 创建代理对象用的
4 LoadBalancerFeignClient 负载均衡客户端 另一个入口中用到了,记住其构造器的第一个参数Client.Default。
5 LoadBalancerFeignClient.Options其本身就是LoadBalancerFeignClient.DEFAULT_OPTIONS
第二个入口才是重点,如果把第一个入口比做提供了一把零件的话,第二个入口就是开启了对这些零件的组装和使用
要想使用Feign必须在启动类中加入@EnableFeignClients注解,这个注解中最主要的就是向容器中注入了一个FeignClientsRegistrar注册器,其registerBeanDefinitions方法就是Feign的第二个入口。代理对象的创建就是在这个入口中发生的
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
//入口方法
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//向容器中注册了一个default.开头的FeignClientSpecification 和第一入口相呼应
registerDefaultConfiguration(metadata, registry);
//主要跟这个方法
registerFeignClients(metadata, registry);
}
//依次将@FeignClient注释的接口定义信息注册到容器
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//构造一个扫描器
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
。。。略
//构造一个寻找FeignClient.class注解的过滤器
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
。。。略
//这里暂时可以理解为在启动类所在的包及其子孙包下 用过滤器进行寻找
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());
//这个name是基于注解配置而来 如果前者为空 那么顺被取后置 优先级如下
//contextId>value>name>serviceId
String name = getClientName(attributes);
//这里会创建一个以 客户端name.开头的FeignClientSpecification 和第一入口 相呼应
registerClientConfiguration(registry, name,
attributes.get("configuration"));
//重点看这里
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
//将bean定义信息注册到容器
//最终定义bean通过FeignClientFactoryBean工厂bean进行创建
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
//重点是这里指定了FeignClientFactoryBean来创建bean
//接下来跟其getObject方法 看看其如何构建bean 构建代理对象的逻辑就在其中
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
//顺位 serviceId>name>value
String name = getName(attributes);
definition.addPropertyValue("name", name);
//顺位 contextId>serviceId>name>value
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);
}
}
registerBeanDefinitions主要做了两件事
1 向容器中注册了一个default.开头的FeignClientSpecification 和第一入口相呼应
2 注册客户端
–2.1 在启动类以及其子孙包下寻找被@FeignClient注释的class
–2.2 为每一个@FeignClient注释的class构建BeanDefinition,并且指定factoryBean为FeignClientFactoryBean
通过IOC的bean创建流程可以知道bean最终会通过FeignClientFactoryBean的getObject方法来创建的
在看FeignClientFactoryBean之前这里先对FeignContext做下总结,因为后边会频繁的使用这个对象。该对象是在第一个入口中注册的
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
public FeignContext() {
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
}
只有一个构造器,内部调用了父类构造器。这里要记住其调用父类构造器时的第一个参数FeignClientsConfiguration,这里存放着子容器的默认配置,后边会用到。
重点看下NamedContextFactory
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
implements DisposableBean, ApplicationContextAware {
//这里可以看到上边的FeignClientsConfiguration.class被赋予到了defaultConfigType中
private Class<?> defaultConfigType;
public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
String propertyName) {
this.defaultConfigType = defaultConfigType;
this.propertySourceName = propertySourceName;
this.propertyName = propertyName;
}
//因为该类是一个ApplicationContextAware 所以其在初始化时容器会将自己注入进来 这个容器时springboot启动时创建的容器 后边会被设置成客户端容器的父容器
private ApplicationContext parent;
//用于保存子容器的集合 key是容器id
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
//还记得入口一中 的那些configuration吗 会调用setConfigurations放进来
private Map<String, C> configurations = new ConcurrentHashMap<>();
public void setConfigurations(List<C> configurations) {
for (C client : configurations) {
this.configurations.put(client.getName(), client);
}
}
//跟到这个方法
public <T> T getInstance(String name, Class<T> type) {
//跟getContext
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) {
//这里new出了容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//这里会先注入@FeignClient 配置的configuration
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
context.register(configuration);
}
}
//这里会注入@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);
}
}
}
//这里注册了this.defaultConfigType 也就是FeignClientsConfiguration.class
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) {
//这里注入了父容器
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;
}
}
看看FeignClientsConfiguration这里都配置了什么
@Configuration(proxyBeanMethods = false)
public class FeignClientsConfiguration {
//请求转化器 来自于自动装配 http包
@Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
@Autowired(required = false)
private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();
@Autowired(required = false)
private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList<>();
//Logger
@Autowired(required = false)
private Logger logger;
@Autowired(required = false)
private SpringDataWebProperties springDataWebProperties;
//解码器 将http响应信息转化成一个对象
//最终会依赖解码器中的this.messageConverters中的MappingJackson2HttpMessageConverter 完成解码
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
return new OptionalDecoder(
new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
}
//编码器
@Bean
@ConditionalOnMissingBean
@ConditionalOnMissingClass("org.springframework.data.domain.Pageable")
public Encoder feignEncoder() {
return new SpringEncoder(this.messageConverters);
}
@Bean
@ConditionalOnClass(name = "org.springframework.data.domain.Pageable")
@ConditionalOnMissingBean
public Encoder feignEncoderPageable() {
PageableSpringEncoder encoder = new PageableSpringEncoder(
new SpringEncoder(this.messageConverters));
if (springDataWebProperties != null) {
encoder.setPageParameter(
springDataWebProperties.getPageable().getPageParameter());
encoder.setSizeParameter(
springDataWebProperties.getPageable().getSizeParameter());
encoder.setSortParameter(
springDataWebProperties.getSort().getSortParameter());
}
return encoder;
}
@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}
@Bean
public FormattingConversionService feignConversionService() {
FormattingConversionService conversionService = new DefaultFormattingConversionService();
for (FeignFormatterRegistrar feignFormatterRegistrar : this.feignFormatterRegistrars) {
feignFormatterRegistrar.registerFormatters(conversionService);
}
return conversionService;
}
//feign重试机制 这里可以看到默认是不重试
@Bean
@ConditionalOnMissingBean
public Retryer feignRetryer() {
return Retryer.NEVER_RETRY;
}
//Feign.Builder
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder().retryer(retryer);
}
@Bean
@ConditionalOnMissingBean(FeignLoggerFactory.class)
public FeignLoggerFactory feignLoggerFactory() {
return new DefaultFeignLoggerFactory(this.logger);
}
@Bean
@ConditionalOnClass(name = "org.springframework.data.domain.Page")
public Module pageJacksonModule() {
return new PageJacksonModule();
}
//这里可以看到如果开启熔断会向容器注入HystrixFeign.builder而不是Feign.Builder
//我这边没开启 所以没注入这个类
@Configuration(proxyBeanMethods = false)
@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();
}
}
}
这里对FeignContext做一个总结。该类主要对外提供getInstance(String name, Class type)接口。该接口会为不同的name创建独立的子容器,然后基于type去子容器中获取对应的bean。具体流程分为三步
1 首次调用该接口时,会针对name创建一个子容器,再次调用如果name是老的就不会在创建子容器,反之会创建。
2 创建的子容器默认向其注册一些configuration,使其容器中拥有一些默认的bean。其中最主要的configuration是FeignClientsConfiguration,其为容器注册了 【Logger、Decoder、Encoder 、Contract、Retryer 、Feign.Builder】等重要的bean,最终设置子容器的父容器为springboot启动时创建的容器
3 最后通过调用子容器的getBean获取bean对象
有了这些铺垫就可以继续阅读 FeignClientFactoryBean的getObject方法了
class FeignClientFactoryBean
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
//入口方法
@Override
public Object getObject() throws Exception {
return getTarget();
}
//从上边跟到这里
<T> T getTarget() {
//拿到FeignContext对象 其是在第一个入口中定义的
FeignContext context = this.applicationContext.getBean(FeignContext.class);
//构建build类 这里发生了子容器的创建
// 其一需要跟这里
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
}
else {
this.url = this.name;
}
this.url += cleanPath();
//这里创建了代理对象
//其二需要跟这里
return (T) loadBalance(builder, context,
new HardCodedTarget<>(this.type, this.name, this.url));
}
//我当前使用的项目 没走到下边的分支 所有略了
...略
}
//构建build对象
protected Feign.Builder feign(FeignContext context) {
//这里首次调用get方法 会为客户端创建子容器 跟下这个get方法
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);
//这里再次出现get 就不会在创建子容器了
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
//将Feign需要的配置从配置对象中拿到build里
configureFeign(context, builder);
return builder;
}
//从容器中拿到了一个对象实例并返回 如果是null抛异常
protected <T> T get(FeignContext context, Class<T> type) {
//跟到FeignContext类中 容器工厂要发挥作用了 基于容器id构建子容器
//这个类其实啥也没干 getInstance方法是其父类NamedContextFactory实现的 所以需要跟NamedContextFactory
T instance = context.getInstance(this.contextId, type);
if (instance == null) {
throw new IllegalStateException(
"No bean found of type " + type + " for " + this.contextId);
}
return instance;
}
//从容器中拿到了一个对象实例并返回 如果是null就返回null
protected <T> T getOptional(FeignContext context, Class<T> type) {
return context.getInstance(this.contextId, type);
}
//看下configureFeign 后续配置优先级啥的可能用得到
protected void configureFeign(FeignContext context, Feign.Builder builder) {
//还记得FeignClientProperties对象吗 入口一中定义了
//所以后边的else分支理论上应该不会走
FeignClientProperties properties = this.applicationContext
.getBean(FeignClientProperties.class);
if (properties != null) {
//defaultToProperties是feign的一个配置参数默认是true
//其主要影响的就是优先级,可以看到if和else中方法的执行顺序不同,后边会覆盖前边
if (properties.isDefaultToProperties()) {
//子容器中的配置先来 优先级最低
// 自动其中的配置来至子容器创建时 为其添加的 其实就是来自于注解【@EnableFeignClients和@FeignClient】
configureUsingConfiguration(context, builder);
//默认配置即feign.client.default 开头的配置 优先级中等
configureUsingProperties(
properties.getConfig().get(properties.getDefaultConfig()),
builder);
//feign.client.{contextId} 开头的配置 优先级最高
configureUsingProperties(properties.getConfig().get(this.contextId),
builder);
}
else {
//这里3个方法和if中的一样 就是顺序不同
//默认配置即feign.client.default 开头的配置 优先级最低
configureUsingProperties(
properties.getConfig().get(properties.getDefaultConfig()),
builder);
//feign.client.{contextId} 开头的配置 优先中等
configureUsingProperties(properties.getConfig().get(this.contextId),
builder);
//子容器中的优先级最高
configureUsingConfiguration(context, builder);
}
}
else {
configureUsingConfiguration(context, builder);
}
}
//这里有个坑
//如果FeignClientProperties.FeignClientConfiguration不同时设置ConnectTimeout和ReadTimeout将不会生效
protected void configureUsingProperties(
FeignClientProperties.FeignClientConfiguration config,
Feign.Builder builder) {
。。。。略
if (config.getConnectTimeout() != null && config.getReadTimeout() != null) {
builder.options(new Request.Options(config.getConnectTimeout(),
config.getReadTimeout()));
}
。。。。略
}
//这个是从容器中获取配置的类
protected void configureUsingConfiguration(FeignContext context,
Feign.Builder builder) {
Logger.Level level = getOptional(context, Logger.Level.class);
if (level != null) {
builder.logLevel(level);
}
Retryer retryer = getOptional(context, Retryer.class);
if (retryer != null) {
builder.retryer(retryer);
}
ErrorDecoder errorDecoder = getOptional(context, ErrorDecoder.class);
if (errorDecoder != null) {
builder.errorDecoder(errorDecoder);
}
//还记得第一入口注册的默认配置吗 这里会获取到
Request.Options options = getOptional(context, Request.Options.class);
if (options != null) {
builder.options(options);
}
Map<String, RequestInterceptor> requestInterceptors = context
.getInstances(this.contextId, RequestInterceptor.class);
if (requestInterceptors != null) {
builder.requestInterceptors(requestInterceptors.values());
}
QueryMapEncoder queryMapEncoder = getOptional(context, QueryMapEncoder.class);
if (queryMapEncoder != null) {
builder.queryMapEncoder(queryMapEncoder);
}
if (this.decode404) {
builder.decode404();
}
ExceptionPropagationPolicy exceptionPropagationPolicy = getOptional(context,
ExceptionPropagationPolicy.class);
if (exceptionPropagationPolicy != null) {
builder.exceptionPropagationPolicy(exceptionPropagationPolicy);
}
}
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
//还记得这个类吗 入口一中注册的 LoadBalancerFeignClient
Client client = getOptional(context, Client.class);
if (client != null) {
//将其放入builder
builder.client(client);
//还记得这个类吗 入口一中注册的 HystrixTargeter
Targeter targeter = get(context, Targeter.class);
//接下来进入HystrixTargeter中 继续跟进
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?");
}
}
这里可以发现其重要方法就是getTarget方法
该方法首先基于feign方法构建了一个Feign.Builder。跟进feign方法发现其内部主要是一个get方法,在跟get方法可以发现其底层调用的就是FeignContext的getInstance方法,其中第一个参数是contextId。到这里应该可以知道【Feign.Builder、Encoder、Decoder、Contract】这些bean其实都来自于子容器的默认配置。
然后是loadBalance方法,该方法将第一个入口中注册的LoadBalancerFeignClient和HystrixTargeter取了出来,然后执行了HystrixTargeter的target方法。
题外话:feign方法有一个configureFeign方法,这个不算主流程但是也挺有用这里总结下。
—配置优先级
(a) feign.client.defaultToProperties = true时 默认的
feign.client.{contextId}配置 > feign.client.default配置 > 子容器配置(注解中声明的)
(b)feign.client.defaultToProperties = true时 默认的
子容器配置(注解中声明的 > feign.client.{contextId}配置 > feign.client.default配置
—onfigureUsingProperties方法中可以看到
如果FeignClientProperties.FeignClientConfiguration不同时设置ConnectTimeout和ReadTimeout将不会生效
—configureUsingConfiguration方法中可以看到
会去容器中获取Request.Options信息,这个Options就是入口一中看到的默认配置
后边继续跟HystrixTargeter
class HystrixTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget<T> target) {
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
//跟这里
//这里的feign是Feign.Builder
return feign.target(target);
}
//这里有一把代码 但是我项目没走这个分支 应该是和熔断有关的 这里不跟了
...略
}
}
HystrixTargeter本来有一些熔断的机制,由于我的项目没用熔断所以下边的分支没有走。继续跟feign.target
跟Feign.Builder
public static class Builder {
//动态代理对象的处理器就是他
private InvocationHandlerFactory invocationHandlerFactory =
new InvocationHandlerFactory.Default();
//入口方法
public <T> T target(Target<T> target) {
//build会创建一个ReflectiveFeign对象
return build().newInstance(target);
}
//基于build构建ReflectiveFeign
public Feign build() {
//synchronousMethodHandlerFactory用于为每一个方法创建增强处理器
//我们定义的接口能够实现远程调用的秘密就在处理器里
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy);
//将处理器工厂封装到了ParseHandlersByName对象中
//该对象的apply方法会为目标类的各个需要代理的方法创建一个SynchronousMethodHandler并返回一个map集合
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
//最终将handlersByName给了ReflectiveFeign
//第二个参数invocationHandlerFactory就是动态代理需要的处理器 马上就会被用到了
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
}
}
看下build方法
1 创建了SynchronousMethodHandler.Factory类,这个工厂类用于构建SynchronousMethodHandler对象。该工厂可以基于目标类的一个方法构建出一个SynchronousMethodHandler。代理类能够具备远程请求能力都靠SynchronousMethodHandler,这个后续在说
2 创建ParseHandlersByName类,内部封装了SynchronousMethodHandler.Factory。该类对SynchronousMethodHandler.Factory的能力进行了封装。该类可以为一个目标类创建一组SynchronousMethodHandler,这组数据会放到一个map中
3 最终构建ReflectiveFeign对象,构造器第一个参数是ParseHandlersByName,第二个参数是InvocationHandlerFactory.Default() 记住这两参数后续会用到
通过分析可以知道newInstance是由ReflectiveFeign对象完成的
public class ReflectiveFeign extends Feign {
ReflectiveFeign(ParseHandlersByName targetToHandlersByName, InvocationHandlerFactory factory,
QueryMapEncoder queryMapEncoder) {
this.targetToHandlersByName = targetToHandlersByName;
this.factory = factory;
this.queryMapEncoder = queryMapEncoder;
}
//代理类在这里被创建
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()) {
//如果是Object的方法不处理
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
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
//构建动态代理处理器 基于methodToHandler
//factory看构造器结合上边的内容就可以知道是InvocationHandlerFactory.Default()
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方法
1 基于构造器的第一个参数对目标类创建了一组SynchronousMethodHandler,一个key是字符串 value是SynchronousMethodHandler的map
2 将第一步的结果封装到另一个map key是方法,value是SynchronousMethodHandler
3 基于构造器的第二个参数创建InvocationHandler对象,还记得第二个参数是InvocationHandlerFactory.Default()吗?
4 最后看到了我们熟悉的动态代理。这里将第二步中封装好的map放到了InvocationHandler里的dispatch熟悉中备用,记住他后续会用到哦
到这里动态代理的对象的创建位置已经找到了
基于动态代理可以知道代理对象的处理逻辑就在InvocationHandler的invoke方法中。这里InvocationHandler是通过factory.create创建的。二这个factory其实就是InvocationHandlerFactory.Default()。所以跟下InvocationHandlerFactory.Default类
public interface InvocationHandlerFactory {
InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch);
/**
* Like {@link InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])}, except for a
* single method.
*/
interface MethodHandler {
Object invoke(Object[] argv) throws Throwable;
}
//在这里
static final class Default implements InvocationHandlerFactory {
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
//跟这里 发现处理器是ReflectiveFeign.FeignInvocationHandler
return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
}
}
}
这里可以看到处理器就是ReflectiveFeign.FeignInvocationHandler,那么接下来就看其invoke方法
static class FeignInvocationHandler implements InvocationHandler {
//目标类的封装HardCodedTarget 从factorybean一直传到了这里
private final Target target;
//之前构建的方法处理器的map集合
private final Map<Method, MethodHandler> dispatch;
FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("equals".equals(method.getName())) {
try {
Object otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
//这里就是通过方法找到方法处理器并调用其invoke方法
//dispatch.get(method)结果就是SynchronousMethodHandler
return dispatch.get(method).invoke(args);
}
}
分析invoke方法,其中dispatch就是构建代理对象时传入handler中的map,最终就是从dispatch中通过method对象拿到对应的SynchronousMethodHandler,然后在执行SynchronousMethodHandler的invoke方法。
final class SynchronousMethodHandler implements MethodHandler {
public Object invoke(Object[] argv) throws Throwable {
//创建模板,这里的buildTemplateFromArgs就是BuildFormEncodedTemplateFromArgs
//RequestTemplate是一个http请求生成器 最终可以生成http请求 里边有url啥的
//options http请求的配置项 还记得配置优先级里提到的这个对象吗
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
//执行跟这里
return executeAndDecode(template, options);
} catch (RetryableException e) {
try {
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 12
response = response.toBuilder()
.request(request)
.requestTemplate(template)
.build();
} catch (IOException 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);
}
if (Response.class == metadata.returnType()) {
...略
}
if (response.status() >= 200 && response.status() < 300) {
if (void.class == metadata.returnType()) {
return null;
} else {
//还记得子容器里注册的那个解码器吗 继续跟这个方法 可以跟到MappingJackson2HttpMessageConverter 和mvc的解码流程一样
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
}
} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
...略
} catch (IOException e) {
...略
}
invoke方法主要做了3件事
1 构建RequestTemplate,该对象可以生成一个请求对象 后边会用到
2 获取请求配置Options
3 执行executeAndDecode方法,这里委托给了client来执行,而这个client其实就是LoadBalancerFeignClient,入口一中注册的
public class LoadBalancerFeignClient implements Client {
//默认的请求参数 第一个入口中提到过哦
static final Request.Options DEFAULT_OPTIONS = new Request.Options();
//记住构造器的第一个参数 被封装到了这个对象里
private final Client delegate;
public LoadBalancerFeignClient(Client delegate,
CachingSpringLoadBalancerFactory lbClientFactory,
SpringClientFactory clientFactory) {
this.delegate = delegate;
this.lbClientFactory = lbClientFactory;
this.clientFactory = clientFactory;
}
//跟这个方法
public Response execute(Request request, Request.Options options) throws IOException {
try {
//获取url
URI asUri = URI.create(request.url());
//获取服务名称 例如【dvs-auth】
String clientName = asUri.getHost();
//这里把url中的服务名称清除了
URI uriWithoutHost = cleanUrl(request.url(), clientName);
//构建ribbon请求对象
//这里将this.delegate传入了 封装到了其client属性中 最最最后边会用到
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
//获取请求配置信息 请求时长和响应时长
IClientConfig requestConfig = getClientConfig(options, clientName);
//这个方法简单介绍下 详细说明会涉及到大量ribbon内容 还没搞明白
//lbClient(clientName)会返回一个feign中的FeignLoadBalancer对象 该对象继承了ribbon中的AbstractLoadBalancerAwareClient对象
//AbstractLoadBalancerAwareClient是ribbon提供的一个负载均衡器 只要继承他 就能调用现成的负载均衡api【executeWithLoadBalancer】
//后边需要稍微跟下AbstractLoadBalancerAwareClient的executeWithLoadBalancer方法
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
//获取请求配置信息 请求时长和响应时长
IClientConfig getClientConfig(Request.Options options, String clientName) {
IClientConfig requestConfig;
//如果默认没修改过 那么以ribbon的配置为准
if (options == DEFAULT_OPTIONS) {
requestConfig = this.clientFactory.getClientConfig(clientName);
}
//如果修改过 以feign为准
else {
requestConfig = new FeignOptionsClientConfig(options);
}
return requestConfig;
}
//这里最终返回了FeignLoadBalancer 这里不深入跟进了 知道返回了FeignLoadBalancer即可
private FeignLoadBalancer lbClient(String clientName) {
return this.lbClientFactory.create(clientName);
}
}
首先这个类是在第一个入口中被注册的,当时说要重点关注构造器第一个参数Client.Default,这里可以看到被封装到了delegate属性中。
为什么要关注这个参数,因为这里边封装了对http请求的实现。后续过程中这个参数在feign和ribbon中来回传,所以需要重点关注下
继续上边的介绍在来关注execute方法,
1 创建URI对象,可以理解为url。此时的url还不是完整的因为内部还是服务名的形式【例如 http://clientname/getxxx】
2 构建FeignLoadBalancer.RibbonRequest对象 这里把Client.Default传入构造函数的第一个参数 重点关注
简单看下RibbonRequest的构造器
protected RibbonRequest(Client client, Request request, URI uri) {
//放到了这里哦
this.client = client;
setUri(uri);
this.request = toRequest(request);
}
3 lbClient(clientName)方法会返回一个FeignLoadBalancer对象,该对象实现了ribbon提供的一个基类并获得了调用负载均衡api的能力
4 最后是executeWithLoadBalancer方法,这个方法就是调用了ribbon的负载均衡api。该方法是由AbstractLoadBalancerAwareClient类完成实现的
//可以看到 这里已经不是feign的包了
package com.netflix.client;
public abstract class AbstractLoadBalancerAwareClient<S extends ClientRequest, T extends IResponse> extends LoadBalancerContext implements IClient<S, T>, IClientConfigAware {
//这里的requestConfig请求配置是我们上边封好传进来的 之后会把他交给子类FeignLoadBalancer
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
//构建一个指令
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
try {
//执行指令
return command.submit(
new ServerOperation<T>() {
@Override
//指令执行会触发这里的回调方法
//这里的server已经是负载均衡后的了,其内部是通过服务名加某种算法【默认是轮询】在服务明对应的若干负载服务中取一个
//Server 这里包含了ip和端口
public Observable<T> call(Server server) {
//将url中的服务名换成真正的ip和端口
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
//这里的AbstractLoadBalancerAwareClient.this其实就是FeignLoadBalancer 所以继续跟FeignLoadBalancer的execute
//所以这里调用了FeignLoadBalancer的execute并把具有完整请求路径的requestForServer和请求配置作为入参
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
} catch (Exception e) {
。。。略
}
}
}
这里很多内容涉及到了ribbon的实现,暂不深入了。这里对executeWithLoadBalancer方法关键步骤做一个简介
1 构建一个指令
2 提交指令,提交指令后ribbon会做负载均衡处理并执行call回调函数,在回调函数中Server类就是负载均衡策略选取的服务,内部包含服务真实的ip和端口。具体ribbon怎么做的这里不在跟进了
3 基于Server对象拼接出最终的可用的url
4 requestForServer就是刚刚提到的FeignLoadBalancer.RibbonRequest ,替换其url为最终的url
5 然后在执行FeignLoadBalancer的execute方法,完成最终的http请求。这里可以发现ribbon只负责了负载均衡,最终的http请求还是交给了feign来完成
package org.springframework.cloud.openfeign.ribbon;
public class FeignLoadBalancer extends
AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> {
//到这里我们已经有了完整的url 就差发送http请求了
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
throws IOException {
Request.Options options;
//configOverride可能不是空了 所以会走这个分支
if (configOverride != null) {
RibbonProperties override = RibbonProperties.from(configOverride);
options = new Request.Options(override.connectTimeout(this.connectTimeout),
override.readTimeout(this.readTimeout));
}
else {
//这里是ribbon的默认配置
options = new Request.Options(this.connectTimeout, this.readTimeout);
}
//这里要执行真正的http请求了
//还记得request.client()是谁吗 就是最最最开头说的Client.Default
Response response = request.client().execute(request.toRequest(), options);
return new RibbonResponse(request.getUri(), response);
}
}
主要看request.client(),这个就是在feign和ribbon中传来传去的Client.Default
继续跟进Client.Default的execute方法
class Default implements Client {
public Response execute(Request request, Options options) throws IOException {
//这个对象是jdk自带的http请求连接对象
HttpURLConnection connection = convertAndSend(request, options);
//发送http请求
return convertResponse(connection, request);
}
}
跟到这里看到其最终调用jdk的工具类完成了http请求的执行
从feign的两个入口,到动态代理对象的创建与执行,到对ribbon负载均衡的引用,最后到http请求的发送。整体做了一个总结