摘要
大家好,我是新手小编王路飞!前段时间因为工作需要,小编对spring-cloud-starter-openfeign组件的源码进行了深度的学习,正所谓独乐乐不如众乐乐(其实是为了复习_),小编将不定时跟大家分享下自己的源码解析成果,希望能对大家有所帮助!
前提
我先来非常笼统的说下feign的实现逻辑,就是在项目启动的时候运用java自带的jdk代理技术来生成代理对象,而我们对他们的客户端接口进行调用时其实是对代理对象的调用,代理对象经过hystrix、ribbon的处理;最后用client对象来进行http请求!(笼统吧,不然后面没得说了*><*)
先添加一波openfeign的maven依赖(版本号就不写了,嘻嘻)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
启动注册
首先我先讲feign客户端是如何注册到spring容器中的,我们从 @EnableFeignClients 启动注解开始讲起,该注解主要定义了feign客户端有效扫描范围和 全局配置信息两个重要的配置项
- value和basePackages:扫描方式,feign客户端扫描范围,范围内的客户端全部启动,与指定方式互斥
- defaultConfiguration:全局配置类,主要有Encoder、Decoder、Retryer、Contract等
- clients:指定方式,主要用于指定启动的客户端,与扫描方式互斥 讲解完属性,我们进入 @EnableFeignClients,我们看到有 @Import(FeignClientsRegistrar.class) 注解,@Import注解的作用主要能够导入FeignClientsRegistrar配置类,FeignClientsRegistrar这个类实现了ImportBeanDefinitionRegistrar接口,这个接口的提供了注册bean的功能(有兴趣可以去了解下,mybatis也用相似的方式实现mapper的注册),而这里主要就是为了注册Feign客户端到spring容器
注册客户端
让我们进到FeignClientsRegistrar这个类中,他只有继承一个方法registerBeanDefinitions()
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
registerDefaultConfiguration:主要用于注册feign范围内的注册全局配置文件
private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 读取属性
Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
// 判断是否有配置全局配置类
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
// 注册配置类
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
关于配置信息类这里有两点可以说下:
1.全局配置类用 default.* 当配置名称,客户端独立配置类用服务名. 当配置名称,优先级:客户端 > 全局
2.配置类统一都用FeignClientSpecification类型进行注册,它包括多个配置信息类和配置名称,最后统一被FeignContext对象引用(feign容器)
registerFeignClients:主要用于注册fign客户端工厂类
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 创建扫描器,扫描全部带着FeignCLient注解的类
// ....
// 判断属性clients是否不为空,如果有clients,则不进行扫描
// ...
// 扫描逻辑开始
for (String basePackage : basePackages) {
// 扫描包
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
// 校验feignClient注解配置的是不是接口
Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
// 解析feignClient配置
Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());
// 获取服务名
String name = getClientName(attributes);
// 注册客户端配置
registerClientConfiguration(registry, name, attributes.get("configuration"));
// 注册feign客户端
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
@FeignClient 主要的几个属性讲解
- name、value、serviceId:三个属性的都是设置服务名,serviceId是弃用属性
- url:请求地址,优先级高于前者
- configuration:客户端独立配置信息类
- fallback 和 fallbackFactory:hystrix熔断处理类(后续我可能会独立分享下hystrix流程)
- path:统一地址前缀
接下来我们主要看下如何注册客户端
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
// 创建feign客户端生成类的bean定义类
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
// 进行@FeignClient属性的提取
// url、path、name三者都可以实现配置注入,感兴趣可深入
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";
// primary属性
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean)attributes.get("primary");
beanDefinition.setPrimary(primary);
// qualifier属性
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias });
// 注册
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
客户端生成的对象实际为FeignClientFactoryBean类,他实现了FactoryBean接口,采用了工厂模式来实现生成不同的bean对象的作用。
到此我说完了注册feign客户端的全部流程;等到spring初始化bean的时候,FeignClientFactoryBean对象将生成具体的代理对象(如何调用请参考jdk代理 )。
生成代理对象
我们直接看FeignClientFactoryBean.getObect(),这个方法就是用来生成代理对象的入口方法
public Object getObject() throws Exception {
// 获取feign容器
FeignContext context = applicationContext.getBean(FeignContext.class);
// 从feign容器中获取构造器, 如果启用了hystrix,会返回HystrixFeign.Builder;
// 生成buidler的同时会进行了一系列组件的配置(配置方式多种,有兴趣可以深入)
Feign.Builder builder = feign(context);
// 判断是否存在url,如果则需要ribbon处理
if (!StringUtils.hasText(this.url)) {
String url;
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
// 获取client进行构建代理对象
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获取client对象
Client client = getOptional(context, Client.class);
// 如果是ribbon的包装对象,直接获取他的委托对象Client.Default
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
client = ((LoadBalancerFeignClient)client).getDelegate();
}
builder.client(client);
}
// 从feign获取targeter对象,如果hystrix启动了,那这里获取的是HystrixTargeter
Targeter targeter = get(context, Targeter.class);
// 开始构建代理对象
return targeter.target(this, builder, context, new HardCodedTarget<>(this.type, this.name, url));
}
这边有三个点,需要详细的解读下
- feignContext对象说明:该类在FeignAutoConfiguration自动配置类中被自动注入,这个主要的作为feign的上下文,存放了feign的全部配置信息,每个客户端都用有独立的applicationContext上下文(同一个服务名的多个接口是为同一个客户端),父级为spring上下文,如果自己的上下文中找不到成员对象,则将会去spring上下文寻找。
- feign.builder对象:该类在FeignAutoConfiguration自动配置类中被自动注入,会根据是否启动hystrix来选择注册Feign.Buidler或者HystrixFeign.Buidler;获取builder后还会将各种成员对象都设置给它,这些成员对象可以通过配置文件和配置类两种进行配置,默认配置文件优先级大于配置类,也可以调整优先级(这些我就先不讲了,以后有时间我再分享下),可进行配置的成员对象主要包含:Contract、Encoder、Decoder、Retryer、LoggerLevel、RequestInterceptor等
- client对象:默认是用LoadBalancerFeignClient包装对象充当client对象,包含ribbon处理,内部最后回去委托Client.Default进行请求,而Default内部就是用UrlConnection发起请求;如果客户端优先为url则不需要ribbon处理,直接取Client.Default当CLient对象
HystrixTargeter对象的逻辑逻辑我就不多讲了,主要就是讲熔断处理类实例化并携带到客户端对象中,主要的逻辑我正在看,之后会专门出一个关于hystrix应用的分享
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget<T> target) {
// 判断是buidler是否是HystrixFeign.Builder,如果不是直接进行跳过熔断处理
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
return feign.target(target);
}
feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
SetterFactory setterFactory = getOptional(factory.getName(), context,
SetterFactory.class);
if (setterFactory != null) {
builder.setterFactory(setterFactory);
}
// 实例化熔断处理类并进行实例化feign对象
Class<?> fallback = factory.getFallback();
if (fallback != void.class) {
return targetWithFallback(factory.getName(), context, target, builder, fallback);
}
// 实例化熔断处理工厂类并进行实例化feign对象
Class<?> fallbackFactory = factory.getFallbackFactory();
if (fallbackFactory != void.class) {
return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
}
}
上面最后一步都会调用builder.target方法,开始生成feign对象
Feign build(final FallbackFactory<?> nullableFallbackFactory) {
// 配置代理处理器工厂类,只有一个生成代理处理器的方法(运用了匿名类技术)
// HystrixInvocationHandler就是代理类的处理器
super.invocationHandlerFactory(new InvocationHandlerFactory() {
@Override public InvocationHandler create(Target target,
Map<Method, MethodHandler> dispatch) {
return new HystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory);
}
});
// 这个对象用于解析各种注解
super.contract(new HystrixDelegatingContract(contract));
// 进行父级构建
return super.build();
}
// 父级feign
public Feign build() {
// 构建方法处理器的工厂对象(接口中每个方法都对应了一个方法处理器)
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel, decode404);
// 创建解析处理器(解析注解并且生成方法处理器)
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, errorDecoder, synchronousMethodHandlerFactory);
// 创建Feign对象
return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
}
ReflectiveFeign对象的主要职责就是生成代理对象
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>();
// 大概的逻辑就是判断是否为原生Object自带的方法,如果是则用DefaultMethodHandler来做方法处理器
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)));
}
}
// 运用工厂对象(build方法中创建)来创建代理处理器
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;
}
代理类生成就流程也差不多全部走完了,当我们正常用接口来做操作时,其中就是在操作HystrixInvocationHandler处理器,,其实我还有很多没讲的,比如说配置的方法和优先级、各种mvc注解的解析等等,因为太多了,有些我打算专门拿出来分享。
下一章预告 : 认识feign(二)请求流程解析
结束词
好了各位,以上就是这篇文章的全部内容了,新手小编文笔不怎么行,希望能帮到大家!!!