OpenFeign源码剖析
feign的核心功能就是通过接口去访问网络资源,里面也是用动态代理来实现的,就跟Mybatis用接口去访问数据库一样,我们就来看下源码的处理,核心就一个包:
3.1 注解处理
使用OpenFeign的时候会用到2个注解,分别是@FeignClient(value = "hailtaxi-driver")和@EnableFeignClients(basePackages = "com.itheima.driver.feign"),这两个注解其实就是学习OpenFeign的入口。
@EnableFeignClients这 个注解的作用其实就是开启了一个FeignClient的扫描,那么点击启动类的@EnableFeignClients注解看下他是怎么开启FeignClient的扫描的,进去后发现里面有个@Import(FeignClientsRegistrar.class)这个FeignClientsRegistrar跟Bean的动态装载有关。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class) //引入了FeignClientsRegistrar类
public @interface EnableFeignClients {}
FeignClientsRegistrar类中有一个方法registerBeanDefinitions用于注入Bean的,源码如下:
/***
* Bean的注入方法
* @param metadata
* @param registry
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
//SpringBoot启动类上检查是否有@EnableFeignClients, 有该注解, 则完成 Feign 框架相关的配置注册
registerDefaultConfiguration(metadata, registry);
//从 classpath 中, 扫描获得 @FeignClient 修饰的类, 将类的内容解析为 BeanDefifinition ,
// 最终通过调用 Spring 框架中的BeanDefifinitionReaderUtils.resgisterBeanDefifinition
// 将解析处理过的 FeignClientBeanDeififinition 添加到 spring 容器中.
registerFeignClients(metadata, registry);
}
我们主要关注registerFeignClients()方法,该方法会通过解析@EnableFeignClients并解析@FeignClient实现Feign的注册,源码如下:
/***
* 注册FeignClients
* @param metadata
* @param registry
*/
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// ClassPath的条件扫描组件提供者
ClassPathScanningCandidateComponentProvider scanner = getScanner();
// 设置资源加载器
scanner.setResourceLoader(this.resourceLoader);
// 要扫描的包(@EnableFeignClients注解上添的那个)
Set<String> basePackages;
// 获取注解上的配置
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
// 注解过滤器,设置只过滤出FeignClient注解标识的Bean
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<String> 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 FeignClientsRegistrar.AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
}
// 循环所有要解析的Feign的包
for (String basePackage : basePackages) {
// 从指定的包中扫描出和规范的BeanDefinition
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
// 扫描的Bean是否是AnnotatedBeanDefinition的子类
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
// 获取beanDefinition的元数据,你想要的他基本都有
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);
// 为FeignClient指定配置类
registerClientConfiguration(registry, name,
attributes.get("configuration"));
// 注册客户端
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
上面注解解析后,会调用registerFeignClient()注册客户端,我们来看下registerFeignClient()方法具体实现流程,代码如下:
/***
* 注册客户端
* @param registry
* @param annotationMetadata
* @param attributes
*/
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
// 被@FeignClient修饰的类名,比如 com.itheima.DriverFeign,是自己定义的接口
String className = annotationMetadata.getClassName();
// BeanDefinitionBuilder通过FeignClientFactoryBean这个类来生成BeanDefinition
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
// 验证fallback和fallbackFactory是不是接口
validate(attributes);
// 通过BeanDefinitionBuilder给beanDefinition增加属性
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";
// 用Builder获取实际的BeanDefinition
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;
}
// 创建一个Bean定义的持有者
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
// 这里就是将Bean注册到Spring容器中
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
3.2 Feign代理注册
上面方法中创建BeanDefinitionBuilder的时候传入了一个参数FeignClientFactoryBean.class,注册的Bean就是参数中自己传进来的beanClass是工厂Bean,可以用来创建Feign的代理对象,我们来看一下FeignClientFactoryBean源码,可以发现它实现了FactoryBean,所以它可以获取对象实例,同时也能创建对象的代理对象,部分源码如下:
它里面有一个方法getObject(),该方法就是用于返回一个对象实例,而对象其实是代理对象,源码如下:
@Override
public Object getObject() throws Exception {
return getTarget();
}
/**
* @param <T> the target type of the Feign client
* @return a {@link Feign} client created with the specified data and the context
* information
*/
<T> T getTarget() {
//FeignContext注册到容器是在FeignAutoConfiguration上完成的
//在初始化FeignContext时,会把configurations在容器中放入FeignContext中。configurations的
//来源就是在前面registerFeignClients方法中将@FeignClient的配置configuration。
FeignContext context = this.applicationContext.getBean(FeignContext.class);
//构建Builder对象
Feign.Builder builder = feign(context);
//如果url为空,则走负载均衡,生成有负载均衡功能的代理类
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));
}
//如果指定了url,则生成默认的代理类
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient) client).getDelegate();
}
if (client instanceof FeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
}
builder.client(client);
}
//生成默认代理类
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
}
3.3 Builder对象
上面片段代码中Feign.Builder builder = feign(context)是用于构建Builder,关于Builder源码属性我们进行详细讲解,源码如下:
public static class Builder {
//这个就是拦截器,可以在请求之前设置请求头、设置请求体、设置参数、设置url等等,类型是:RequestInterceptor:
private final List<RequestInterceptor> requestInterceptors =
new ArrayList<RequestInterceptor>();
//日志等级
private Logger.Level logLevel = Logger.Level.NONE;
//默认是Contract.Default(),它主要是用来解析feign接口上的那些注解,比如:@QueryMap、@Param、@RequestLine、@Header、@Body、@HeaderMap等,比如@Header操作,可以把@Header(“name=value”)这里面的name=value取出来,重新设置到RequestTemplate里面。
private Contract contract = new Contract.Default();
//client是真正去执行request,得到response的客户端,它的入参是一个Request,这个Request是用RequestTemplate构造出来的。
private Client client = new Client.Default(null, null);
private Retryer retryer = new Retryer.Default();
private Logger logger = new NoOpLogger();
//encoder是用来编码请求的,默认能处理String和byte[]数组类型的参数,最终是存放在RequestTemplate里面。
private Encoder encoder = new Encoder.Default();
//decoder用来解码响应,默认可以返回字节数组和字符串。
private Decoder decoder = new Decoder.Default();
private QueryMapEncoder queryMapEncoder = new QueryMapEncoder.Default();
//异常处理
private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
private Options options = new Options();
//就是在这里面创建的动态代理类。当客户端调用Feign.builder()的时候,其实就是去设置builder里面的这些参数的值。
private InvocationHandlerFactory invocationHandlerFactory =
new InvocationHandlerFactory.Default();
private boolean decode404;
private boolean closeAfterDecode = true;
private ExceptionPropagationPolicy propagationPolicy = NONE;
}
builder构建如下方法:
/****
* 构建Builder独享
* @param context
* @return
*/
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);
// @formatter:off
Feign.Builder builder = get(context, Feign.Builder.class)
// 设置日志等级
.logger(logger)
//设置编码
.encoder(get(context, Encoder.class))
//设置解码
.decoder(get(context, Decoder.class))
//设置contract
.contract(get(context, Contract.class));
// @formatter:on
configureFeign(context, builder);
return builder;
}
3.4 Feign代理创建
上面的builder构造完后继续向下走,配置完Feign.Builder之后,再判断是否需要LoadBalance,如果需要,则通过loadBalance(builder, context,new HardCodedTarget<>(this.type, this.name, this.url));的方法来设置。实际上他们最终调用的是Target.target()方法。
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
//获取一个服务的client
Client client = getOptional(context, Client.class);
if (client != null) {
//将client设置进去相当于增加了客户端负载均衡解析的机制
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?");
}
上面方法会调用targeter.target(this, builder, context, target);,它支持服务熔断降级,我们直接看默认的DefaultTrageter就可以了。
DefaultTargeter的target()方法是一个非常简单的调用,但开启了Feign代理对象创建的开始:
target方法调用了build().newInstance(),这个方法信息量比较大,我们要拆分这看build()和newInstance(target):
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
build()方法是创建客户端对象ReflectiveFeign,看着名字就像代理的意思,源码如下:
/***
* 就是构造Feign客户端对象ReflectiveFeign,并且添加默认值,
* 注意下synchronousMethodHandlerFactory创建出来的MethodHandler的类型是SynchronousMethodHandler。
* handlersByName可以把feign接口解析成Map<String, MethodHandler>。
* @return
*/
public Feign build() {
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy);
ReflectiveFeign.ParseHandlersByName handlersByName =
new ReflectiveFeign.ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
我们再来看ReflectiveFeign,它继承了Feign同时也有一个属性InvocationHandlerFactory,该对象其实就是代理工厂对象,源码如下:
ReflectiveFeign源码:
InvocationHandlerFactory源码:
我们再来看newInstance(Target<T> target)方法,该方法就是用来创建Feign的代理对象,源码如下:
/***
* 创建代理
* @param target
* @param <T>
* @return
*/
public <T> T newInstance(Target<T> target) {
//根据接口类和Contract协议解析方式,解析接口类上的方法和注解,转换成内部的MethodHandler处理方式
Map<String, InvocationHandlerFactory.MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, InvocationHandlerFactory.MethodHandler> methodToHandler = new LinkedHashMap<Method, InvocationHandlerFactory.MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<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)));
}
}
// 基于Proxy.newProxyInstance 为接口类创建动态实现,将所有的请求转换给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.5 远程请求
远程请求一定是要有IP和端口的,OpenFeign将IP和端口封装到RequestTemplate中了,我们来看一下RequestTemplate源码:
在SynchronousMethodHandler类中执行远程调用,源码如下:
/**
* 远程调用
* @param argv
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object[] argv) throws Throwable {
//封装成RequestTemplate
RequestTemplate template = buildTemplateFromArgs.create(argv);
Request.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;
}
}
}
上面调用会调用executeAndDecode()方法,该方法是执行远程请求,同时解析响应数据,源码如下:
/****
* 发起远程请求
* @param template
* @param options
* @return
* @throws Throwable
*/
Object executeAndDecode(RequestTemplate template, Request.Options options) throws Throwable {
//转换为HTTP请求报文
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);
} 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);
}
if (Response.class == metadata.returnType()) {
if (response.body() == null) {
return response;
}
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 {
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
}
} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
} 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());
}
}
}