本文作者:陈刚,叩丁狼高级讲师。原创文章,转载请注明出处。
回顾
我们还是在之前Feign的使用案例集成上来分析Feign源码,在分析 之前我们先简单来回顾一下Feign的用法,要用Feign首选是需要在配置类似开启Feign客户端支持
@EnableEurekaClient
@SpringBootApplication
@EnableFeignClients
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
然后我们需要去定义Feign客户端接口
//PRODUCER:指向要访问的服务
//configuration = FooConfiguration.class Feign Client配置类,指定负载均衡策略
//fallback = MyFeignClientImpl.class: MyFeignClient结构的实现类,需要复写 provide方法,并实现错误处理逻辑
//当访问出现故障,程序会自动调用实现类的 provide方法的错误处理逻辑。
@FeignClient(value = "PRODUCER",configuration = FooConfiguration.class,fallback = MyFeignClientImpl.class)
public interface MyFeignClient {
//当此方法别调用会自动请求到 PRODUCER服务的 /provide 资源
@RequestMapping(value = "/provide")
public String provide(@RequestParam("name") String name);
}
在使用的时候就只需要调用 MyFeignClient 接口即可,SpringCloud会自动去调用对应的目标服务。
分析Feign我们还是从他的@EnableFeignClients标签开始,我们先看下他的源码
/**
* 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 {
该标签的注释告诉我们,它开启了对打了 @FeignClient 标签所在的接口的扫描,和对期配置组件的扫描支持 ,并且该标签import了一个FeignClientsRegistrar类,我们跟踪进去看一下
/**
* @author Spencer Gibb
* @author Jakub Narloch
* @author Venil Noronha
* @author Gang Li
*/
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
ResourceLoaderAware, EnvironmentAware {
// patterned after Spring Integration IntegrationComponentScanRegistrar
// and RibbonClientsConfigurationRegistgrar
private ResourceLoader resourceLoader;
private Environment environment;
public FeignClientsRegistrar() {
他实现了 ImportBeanDefinitionRegistrar接口,我们继续看ImportBeanDefinitionRegistrar的源码
/**
* Interface to be implemented by types that register additional bean definitions when
* processing @{@link Configuration} classes. Useful when operating at the bean definition
* level (as opposed to {@code @Bean} method/instance level) is desired or necessary.
*
* Along with {@code @Configuration} and {@link ImportSelector}, classes of this type
* may be provided to the @{@link Import} annotation (or may also be returned from an
* {@code ImportSelector}).
*
*
An {@link ImportBeanDefinitionRegistrar} may implement any of the following
* {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective
* methods will be called prior to {@link #registerBeanDefinitions}:
*
* - {@link org.springframework.context.EnvironmentAware EnvironmentAware}
* - {@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}
*
- {@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}
*
- {@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}
*
*
* See implementations and associated unit tests for usage examples.
*
* @author Chris Beams
* @since 3.1
* @see Import
* @see ImportSelector
* @see Configuration
*/
public interface ImportBeanDefinitionRegistrar {
/**
* Register bean definitions as necessary based on the given annotation metadata of
* the importing {@code @Configuration} class.
*
Note that {@link BeanDefinitionRegistryPostProcessor} types may not be
* registered here, due to lifecycle constraints related to {@code @Configuration}
* class processing.
* @param importingClassMetadata annotation metadata of the importing class
* @param registry current bean definition registry
*/
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
翻译“Interface to be implemented by types that register additional bean definitions when processing @{@link Configuration} classes.”得知,该接口提供了注册bean的功能支持,而registerBeanDefinitions则是bean注册的方法。 AnnotationMetadata是bean的元注解对象 ,BeanDefinitionRegistry是当前注册的bean的注册表,
我们继续跟踪一下 FeignClientsRegistrar#registerBeanDefinitions 方法
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
registerDefaultConfiguration(metadata, registry);注册默认配置,我们跟踪一下源码
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();
}
//name以 "default."+类名
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
这里获取了主程序配置类的@EnableFeignClients注解中的feign默认配置 ,然后用 “default.” 开头加上类名作为名字来注册 ,继续跟踪
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
这里把 configuration 配置交给了 BeanDefinitionBuilder然后去创建了一个 FeignClientSpecification 对象出来 ,然后调用BeanDefinitionRegistry的registerBeanDefinition方法把 包装了配置类的FeignClientSpecification对象进行注册,注册到哪儿去了呢?最终会调用BeanDefinitionRegistryd的子类DefaultListableBeanFactory#registerBeanDefinition方法注册
...省略代码...
/** Map of bean definition objects, keyed by bean name */
private final Map beanDefinitionMap = new ConcurrentHashMap<>(256);
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
...省略代码...
this.beanDefinitionMap.put(beanName, beanDefinition);
这里最终会put到一个线程安全的map:beanDefinitionMap = new ConcurrentHashMap<>(256); 中,这个东西就是ico容器o( ̄︶ ̄)o 。
回过头来我们继续看 FeignClientsRegistrar#registerBeanDefinitions 方法
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//注册默认配置到ioc容器中
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
到这里我们已经知道 registerDefaultConfiguration(metadata, registry);注册默认配置,那么 registerFeignClients(metadata, registry);方法看名字应该是注册所有的feignClient的bean,我们一步一步进去看
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//得到组件扫描器,resourceLoader是资源加载器,
//比如加载spring配置文件就会用到ResourceLoader
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set basePackages;
//获取元注解上EnableFeignClients的属性配置
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) {
//扫描候选的组件,会把basePackage报下的bean都扫描到,然后过滤出打了 @FeignClient 标签的接口,封装成 BeanDefinition bean描述对象
Set candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
//获取FeignClient接口上的的注解(我定义的FeignClient接口是MyFeignClient)
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
//如果贴上 @FeignClient标签的不是接口,抛出异常
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
//获取到@FeignClient 注解上我们配置的所有属性
Map attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
//获取@FeignClient标签的服务名我这指定是“PRODUCER”
//(@FeignClient(value = "PRODUCER",configuration = FooConfiguration.class,fallback = MyFeignClientImpl.class))
String name = getClientName(attributes);
//注册配置对象:即会获取到configuration = FooConfiguration.class然后实现注册(将bean封装成beanDefinition对象放到ioc容器中),
//而FooConfiguration中我们自己实现了负载均衡规则
registerClientConfiguration(registry, name,
attributes.get("configuration"));
//注册 registerFeign
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
这里面做的事情挺多的
1.通过组件扫描器ClassPathScanningCandidateComponentProvider
2.获取元注解所在配置类的包名,
3.然后通过ClassPathScanningCandidateComponentProvider扫描打了@FeignClient标签的接口 ,然后封装成 Set
4.获取 FeignClient 接口上的 @FeignClient标签的配置类,完成注册
5.完成 FeignClient接口本身的注册
我们继续跟踪 registerFeignClient 方法
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map attributes) {
String className = annotationMetadata.getClassName();
//这里使用了FeignClientFactoryBean
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
//这里把FeignClient的所有配置属性都设置给了 FeignClientFactoryBean
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);
//取别名 FeignClient结尾
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 });
//注册 FeignClientFactoryBean
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
这里使用 FeignClientFactoryBean来封装 FeignClient的描述信息 ,并且注册到ioc容器中 ,使用FeignClientFactoryBean 的目的是为了在spring容器刷新过程中会使用代理工厂来创建出代理类(你是否想过,我们的MyFeignClient始终是个接口,那么他是如何实现远程服务的调用的?肯定需要创建出具体的实例去处理),我们跟踪一下 FeignClientFactoryBean
class FeignClientFactoryBean implements FactoryBean
首先FeignClientFactoryBean实现了 FactoryBean ,而FactoryBean就是spring提供给我们实例化bean的一种方式,bean的实例化过程在 getObject 方法中进行处理 ,而在这里他在getObject方法中调用getTarget()方法。
这个方法里面做了很多事情
1.首先获取FeignContext feigin上下文对象,可以把它理解为是feign的容器对象,而FeignContext使用FeignClientsConfiguration进行配置,在FeignClientsConfiguration中会把feignClient相关的配置绑定到FeignContext中
public class FeignContext extends NamedContextFactory {
public FeignContext() {
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
}
----------------------------------------------------------------------
@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
public class FeignAutoConfiguration {
@Autowired(required = false)
private List configurations = new ArrayList<>();
@Bean
public HasFeatures feignFeature() {
return HasFeatures.namedFeature("Feign", Feign.class);
}
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
//绑定配置
context.setConfigurations(this.configurations);
return context;
}
2.Feign.Builder builder = feign(context); 获取Feign构建器
3.判断 url是否为null,如果为null则创建负载均衡的代理对象
4.如果url不为null,创建默认的代理对象
我们继续跟踪一下 feign(context)方法
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);
//获取Feign.Builder
// @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;
}
............
protected void configureFeign(FeignContext context, Feign.Builder builder) {
FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);
if (properties != null) {
if (properties.isDefaultToProperties()) {
configureUsingConfiguration(context, builder);
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(this.name), builder);
} else {
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(this.name), builder);
configureUsingConfiguration(context, builder);
}
} else {
configureUsingConfiguration(context, builder);
}
}
............
protected void configureUsingConfiguration(FeignContext context, Feign.Builder builder) {
Logger.Level level = getOptional(context, Logger.Level.class);
if (level != null) {
builder.logLevel(level);
}
//设置重试策略到 Feign.Builder
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 requestInterceptors = context.getInstances(
this.name, RequestInterceptor.class);
if (requestInterceptors != null) {
builder.requestInterceptors(requestInterceptors.values());
}
if (decode404) {
builder.decode404();
}
}
这里获取到 builder 之后又做了后续的一些设置,比如重试策略 ,错误处理,请求超时时间设定等 。
我们跟踪一下 Feign.Builder builder = get(context, Feign.Builder.class) 方法
protected T get(FeignContext context, Class type) {
//调用 TraceFeignContext的getInstance方法,根据name即服务的名字来创建builder
T instance = context.getInstance(this.name, type);
if (instance == null) {
throw new IllegalStateException("No bean found of type " + type + " for "
+ this.name);
}
return instance;
}
...............................
public T getInstance(String name, Class type) {
AnnotationConfigApplicationContext context = getContext(name);
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
type).length > 0) {
//创建builder
return context.getBean(type);
}
return null;
}
...............................
protected AnnotationConfigApplicationContext getContext(String name) {
//这里的参数name就是目标服务的名字,如果contexts中没有就创建一个放进去
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
this.contexts.put(name, createContext(name));
}
}
}
return this.contexts.get(name);
}
这里在根据被调用的目标服务的名字判断contexts中是否存在实例(Map
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//加载服务对应的配置类
if (this.configurations.containsKey(name)) {
for (Class> configuration : this.configurations.get(name)
.getConfiguration()) {
context.register(configuration);
}
}
//加载主程序配置类的@EnableFeignClients注解指定的配置, 还记得最开始会扫描配置类并以“default”开头为key注册到ioc中吗?
for (Map.Entry entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
//注册默认的配置类
context.register(PropertyPlaceholderAutoConfiguration.class,
this.defaultConfigType);
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
this.propertySourceName,
Collections. singletonMap(this.propertyName, name)));
if (this.parent != null) {
// Uses Environment from parent as well as beans
context.setParent(this.parent);
}
context.setDisplayName(generateDisplayName(name));
context.refresh();
return context;
}
我们可以看到这里每调用一个次 createContext方法就会根据服务名字去创建一个新的AnnotationConfigApplicationContext 容器对象,然后获取Feign的配置类给context绑定 ,最后会调用context.refresh();容器刷新,最后会调用 context.getBean(type); 创建出一个 FeignBuilder出来
回到我们的主线 org.springframework.cloud.openfeign.FeignClientFactoryBean#getTarget方法 ,我们看下 return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,this.name, url));部分是如何创建负载均衡的代理类的
protected T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget target) {
//获得FeignClient
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
Targeter targeter = get(context, Targeter.class);
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?");
}
Client client = getOptional(context, Client.class); 这里会调用TraceFeignContext#getInstance方法获取到 Client对象,而Client有三个子实现 ,当我们使用了Feign ,这里会使用LoadBalancerFeignClient ,看名字就能知道他是一个负载均衡的FeignClient客户端 ,我们跟踪一下targeter.target,这里在创建代理类
class HystrixTargeter implements Targeter {
@Override
public T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget target) {
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
return feign.target(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, closeAfterDecode);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
}
这里调用到了 HystrixTargeter的target方法,调用 Feign的target方法,通过 ReflectiveFeign 工具类的 newInstance 创建实例
public T newInstance(Target target) {
//target封装了FeignClient ,这里在获取FeignClient接口的方法封装为MethodHandler
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,接收请求,转发到methodHandler
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;
}
这里在获取 FeignClient接口的方法,然后封装到 MethodHandler ,如果继续跟踪targetToHandlersByName.apply(target);你会看到他底层对FeignClient的元注解做了解析
public Map apply(Target key) {
//解析FeignClient接口上的注解,把配置信息封装到MethodMetadata,
//并且MethodMetadata中会有一个RequestTemplate对象会封装好要请求的服务名,参数类型等信息
List metadata = contract.parseAndValidatateMetadata(key.type());
Map result = new LinkedHashMap();
for (MethodMetadata md : metadata) {
BuildTemplateByResolvingArgs buildTemplate;
if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
} else if (md.bodyIndex() != null) {
buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
} else {
//根据元注解创建出BuildTemplateByResolvingArgs对象,而这个东西其实就是RequestTemplate的工厂
buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder);
}
//这里在创建 MethodHandler ,
result.put(md.configKey(),
factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
}
return result;
}
,获取到MethodHandler,然后根据 FeignClient 时间jdk动态代理创建出代理类
InvocationHandler handler = factory.create(target, methodToHandler);的底层会把 target和 methodToHandler封装到 FeignInvocationHandler ,而FeignInvocationHandler就是对InvocationHandler的实现,当我们在代码中调用FeignClient接口实现远程调用时,代理类就会被执行,即 FeignInvocationHandler的invoke接受到请求时就会调用 methodToHandler的方法 。源码如下
static class FeignInvocationHandler implements InvocationHandler {
private final Target target;
private final Map dispatch;
FeignInvocationHandler(Target target, Map dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
}
@Override
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();
}
return dispatch.get(method).invoke(args);
}
dispatch.get(method).invoke(args) 这里在获取要执行的方法(MethodHandler),然后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;
}
}
}
可以看到这里创建了RequestTemplate请求模板 ,然后调用executeAndDecode执行请求,并且对重试机制做了处理,我们跟踪一下buildTemplateFromArgs.create(argv);
@Override
public RequestTemplate create(Object[] argv) {
//创建请求模板,metadata.template()获取到的就是一个RequestTemplate,只是这里面的RequestTemplate对请求服务id,参数等做了封装
RequestTemplate mutable = new RequestTemplate(metadata.template());
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 varBuilder = new LinkedHashMap();
for (Entry> entry : metadata.indexToName().entrySet()) {
int i = entry.getKey();
Object value = argv[entry.getKey()];
if (value != null) { // Null values are skipped.
if (indexToExpander.containsKey(i)) {
value = expandElements(indexToExpander.get(i), value);
}
//这里在把请求参数放到map中
for (String name : entry.getValue()) {
varBuilder.put(name, value);
}
}
}
//解析处理请求地址,参数,和编码等
RequestTemplate template = resolve(argv, mutable, varBuilder);
if (metadata.queryMapIndex() != null) {
// add query map parameters after initial resolve so that they take
// precedence over any predefined values
Object value = argv[metadata.queryMapIndex()];
Map queryMap = toQueryMap(value);
template = addQueryMapQueryParameters(queryMap, template);
}
if (metadata.headerMapIndex() != null) {
template = addHeaderMapHeaders((Map) argv[metadata.headerMapIndex()], template);
}
return template;
}
这里在封装RequestTemplate请求模板和处理请求地址和参数,我们继续跟踪 executeAndDecode 方法,看一下是如何执行的
Object executeAndDecode(RequestTemplate template) throws Throwable {
//获取请求对象
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
//调用LoadBalancerFeignClient负载均衡器客户端执行请求
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 10
response.toBuilder().request(request).build();
} catch (IOException e) {
...省略代码...
这里调用了 LoadBalancerFeignClient的execute方法
@Override
public Response execute(Request request, Request.Options options) throws IOException {
try {
//得到完整的请求地址 http://xxxx/xx?xx=xx
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
//构建 FeignLoadBalancer.RibbonRequest Feign负载均衡的请求对象
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
//客户端配置
IClientConfig requestConfig = getClientConfig(options, clientName);
//发送请求
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
requestConfig).toResponse();
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
-------------------------------------------------
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
LoadBalancerCommand command = buildLoadBalancerCommand(request, requestConfig);
try {
return command.submit(
new ServerOperation() {
@Override
public Observable call(Server server) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
} catch (Exception e) {
Throwable t = e.getCause();
if (t instanceof ClientException) {
throw (ClientException) t;
} else {
throw new ClientException(e);
}
}
}
这里调用AbstractLoadBalancerAwareClient#executeWithLoadBalancer 方法名的大致意思是 使用负载均衡的方式去执行请求 ,而call(Server server) 的Server这是进行了负载均衡选择后的调用目标服务,那么是如何进行选择的呢,我们跟踪一下 command.submit方法
com.netflix.loadbalancer.reactive.LoadBalancerCommand#submit:
============================================================
public Observable submit(final ServerOperation operation) {
final ExecutionInfoContext context = new ExecutionInfoContext();
if (listenerInvoker != null) {
try {
listenerInvoker.onExecutionStart();
} catch (AbortExecutionException e) {
return Observable.error(e);
}
}
final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer();
final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer();
// Use the load balancer
//选择要调用的目标服务
Observable o =
(server == null ? selectServer() : Observable.just(server))
.concatMap(new Func1>() {
@Override
// Called for each server being selected
public Observable call(Server server) {
context.setServer(server);
final ServerStats stats = loadBalancerContext.getServerStats(server);
selectServer()在选择调用的服务 ,我们跟踪进去
com.netflix.loadbalancer.reactive.LoadBalancerCommand#selectServer
============================================================
private Observable selectServer() {
return Observable.create(new OnSubscribe() {
@Override
public void call(Subscriber super Server> next) {
try {
//从LoadBalancer中选择服务
Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);
next.onNext(server);
next.onCompleted();
} catch (Exception e) {
next.onError(e);
}
}
});
}
---------------------------------------
com.netflix.loadbalancer.LoadBalancerContext#getServerFromLoadBalancer
public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
String host = null;
int port = -1;
if (original != null) {
host = original.getHost();
}
if (original != null) {
Pair schemeAndPort = deriveSchemeAndPortFromPartialUri(original);
port = schemeAndPort.second();
}
// Various Supported Cases
// The loadbalancer to use and the instances it has is based on how it was registered
// In each of these cases, the client might come in using Full Url or Partial URL
ILoadBalancer lb = getLoadBalancer();
if (host == null) {
// Partial URI or no URI Case
// well we have to just get the right instances from lb - or we fall back
if (lb != null){
//选择服务,lb就是LoadBalancer
Server svc = lb.chooseServer(loadBalancerKey);
if (svc == null){
throw new ClientException(ClientException.ErrorType.GENERAL,
"Load balancer does not have available server for client: "
+ clientName);
}
host = svc.getHost();
if (host == null){
throw new ClientException(ClientException.ErrorType.GENERAL,
"Invalid Server for :" + svc);
}
logger.debug("{} using LB returned Server: {} for request {}", new Object[]{clientName, svc, original});
return svc;
......
============================================================
public class ZoneAwareLoadBalancer extends DynamicServerListLoadBalancer {
......
//选择服务
@Override
public Server chooseServer(Object key) {
if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
logger.debug("Zone aware logic disabled or there is only one zone");
//选择服务
return super.chooseServer(key);
}
Server server = null;
try {
LoadBalancerStats lbStats = getLoadBalancerStats();
Map zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
......
public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule {
@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
//选择服务
Optional server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
----------------------------------------
/**
* Choose a server in a round robin fashion after the predicate filters a given list of servers and load balancer key.
轮询的方式选择服务
*/
public Optional chooseRoundRobinAfterFiltering(List servers, Object loadBalancerKey) {
List eligible = getEligibleServers(servers, loadBalancerKey);
if (eligible.size() == 0) {
return Optional.absent();
}
return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));
}
看到这里你是否看到了熟悉的代码,没错我们一路跟踪最终发现这里的服务选择和上一章Robin的负载均衡选择服务的代码是一样的 ,因为 Feign默认是集成了Ribbon技术默认轮询,当然也可以添加自己的负载均衡规则
远程调用服务我们已经知道是如何进行选择的了,我们继续跟踪一下看下是怎么调用服务的,回到主线 FeignLoadBalancer. execute
----------------------------------------------------
public class FeignLoadBalancer extends
AbstractLoadBalancerAwareClient {
...省略代码......
@Override
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
throws IOException {
Request.Options options;
if (configOverride != null) {
RibbonProperties override = RibbonProperties.from(configOverride);
options = new Request.Options(
override.connectTimeout(this.connectTimeout),
override.readTimeout(this.readTimeout));
}
else {
options = new Request.Options(this.connectTimeout, this.readTimeout);
}
Response response = request.client().execute(request.toRequest(), options);
return new RibbonResponse(request.getUri(), response);
}
.......方法调用链太长.省略......
public static class Default implements Client {
private final SSLSocketFactory sslContextFactory;
private final HostnameVerifier hostnameVerifier;
/**
* Null parameters imply platform defaults.
*/
public Default(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier) {
this.sslContextFactory = sslContextFactory;
this.hostnameVerifier = hostnameVerifier;
}
@Override
public Response execute(Request request, Options options) throws IOException {
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection).toBuilder().request(request).build();
}
//发送请求啦,最终调用了feign.Client.Default#convertAndSend方法使用HttpUrlConnection发送请求
HttpURLConnection convertAndSend(Request request, Options options) throws IOException {
final HttpURLConnection
connection =
(HttpURLConnection) new URL(request.url()).openConnection();
if (connection instanceof HttpsURLConnection) {
HttpsURLConnection sslCon = (HttpsURLConnection) connection;
if (sslContextFactory != null) {
sslCon.setSSLSocketFactory(sslContextFactory);
}
if (hostnameVerifier != null) {
sslCon.setHostnameVerifier(hostnameVerifier);
}
}
connection.setConnectTimeout(options.connectTimeoutMillis());
connection.setReadTimeout(options.readTimeoutMillis());
connection.setAllowUserInteraction(false);
connection.setInstanceFollowRedirects(options.isFollowRedirects());
connection.setRequestMethod(request.method());
Collection contentEncodingValues = request.headers().get(CONTENT_ENCODING);
boolean
gzipEncodedRequest =
contentEncodingValues != null && contentEncodingValues.contains(ENCODING_GZIP);
boolean
deflateEncodedRequest =
contentEncodingValues != null && contentEncodingValues.contains(ENCODING_DEFLATE);
boolean hasAcceptHeader = false;
Integer contentLength = null;
for (String field : request.headers().keySet()) {
if (field.equalsIgnoreCase("Accept")) {
hasAcceptHeader = true;
}
for (String value : request.headers().get(field)) {
if (field.equals(CONTENT_LENGTH)) {
if (!gzipEncodedRequest && !deflateEncodedRequest) {
contentLength = Integer.valueOf(value);
connection.addRequestProperty(field, value);
}
} else {
connection.addRequestProperty(field, value);
}
}
}
// Some servers choke on the default accept string.
if (!hasAcceptHeader) {
connection.addRequestProperty("Accept", "*/*");
}
if (request.body() != null) {
if (contentLength != null) {
connection.setFixedLengthStreamingMode(contentLength);
} else {
connection.setChunkedStreamingMode(8196);
}
connection.setDoOutput(true);
OutputStream out = connection.getOutputStream();
if (gzipEncodedRequest) {
out = new GZIPOutputStream(out);
} else if (deflateEncodedRequest) {
out = new DeflaterOutputStream(out);
}
try {
out.write(request.body());
} finally {
try {
out.close();
} catch (IOException suppressed) { // NOPMD
}
}
}
return connection;
}
最终还是使用了HttpURLConnection来实现远程调用
好吧大致总结一下步骤:
EnableFeignClients开启 feign功能后会调用 FeignClientsRegistrar 的registerBeanDefinitions方法来给bean的注册
FeignClientsRegistrar.registerBeanDefinitions方法中会调用 FeignClientsRegistrar .registerDefaultConfiguration方法通过获取主程序配置类的原始注解扫描到Feign的配置类,并以“default”开头为key对配置类进行注册(在给 feign代理上下文对象时会用到)
3.然后会调用 FeignClientsRegistrar .registerBeanDefinitions方法中会调用 FeignClientsRegistrar. registerFeignClients方法去注册FeignClient(生成代理类时当然要知道FeignClient接口是哪个)
4.扫描并获取到FeiginClient 接口,然后获取到FeignClient的配置相关属性进行注册(这里的注册其实就是用一个map存储起来,后面拿来用)
5.使用 FeignClientFactoryBean 对FeignClient进行封装(设置FeignClient的配置属性等),然后再使用BeanDefinitionHolder进行包裹,最后交给BeanDefinitionReaderUtils.registerBeanDefinition进行注册
6.而 FeignClientFactoryBean 中的 getObject 方法根据FeignClient的配置属性中的url时候有值来决定是否创建负载均衡的FeignClient接口的代理 ,如果咩有设置url者创建负载均衡的FeignClient代理实例
7.底层使用jdk动态代理去创建代理类,底层调用是用到了LoadBalancerFeignClient负载均衡客户端,调用FeignLoadBalancer.execute ,使用Ribbon的负载均衡选择服务,最终使用到HttpURLConnection发送请求
想获取更多技术干货,请前往叩丁狼官网:http://www.wolfcode.cn/all_article.html