OpenFeign其实还是负责通信的,在使用过程中,让开发者可以面向接口开发的方式进行远程调用,那么:
- OpenFeign的注解是如何被扫描解析的呢?
- 注解标注的接口注入到IoC中的实例是什么?
- OpenFeign的上下文是如何构建的呢?
- 远程通信是如何实现的呢?与RestTemplate有关系么?
带着以上的问题,准备开始Feign的源码,同样的 在Spring的生态下,切入点还是那几个,
EnableFeignClients
和FeignAutoConfiguration
,其实 如果看一眼spring-cloud-openfeign-core
里面的spring.factories
就发现 这里面其实有好多AutoConfiguration。
OpenFeign初始化
@EnableFeignClients
这个注解
/** * Scans for interfaces that declare they are feign clients (via * {@link org.springframework.cloud.openfeign.FeignClient}
@FeignClient
). * Configures component scanning directives for use with * {@link org.springframework.context.annotation.Configuration} *@Configuration
classes. * 扫描所有标注了@FeignClient的接口 将其配置成配置类 */ @Import(FeignClientsRegistrar.class) public @interface EnableFeignClients { .... }这时候 关注点就应该在
FeignClientsRegistrar
FeignClientsRegistrar
虽然没有在任何的配置中看到 实例化它,但是它实例化的还是很早的,在 org.springframework.context.annotation.ConfigurationClassParser#processImports
registerBeanDefinitions
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
....
// 重点关注这里 ImportBeanDefinitionRegistrar#registerBeanDefinitions
// 两个参数分别是StandardAnnotationMetadata, DefaultListableBeanFactory
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 如果注解中的defaultConfiguration不为空 则将其注入到IoC容器
registerDefaultConfiguration(metadata, registry);
// 这里是注入FeignClient的地方
registerFeignClients(metadata, registry);
}
....
}
registerFeignClients
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
....
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
LinkedHashSet candidateComponents = new LinkedHashSet<>();
// 获取 @EnableFeignClients 所有的配置信息
Map attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
// 获取 @EnableFeignClients.clients 的值
final Class>[] clients = attrs == null ? null
: (Class>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
// 这个集合必然不为空 分别取了value、basePackages、basePackageClasses
// 如果那三个为空 则将标注了@EnableFeignClients 的类所在的路径加入到这个集合
Set basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
} else {
for (Class> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
// 遍历 并生成代理类 并注入到IoC容器
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 attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
// @FeignClient的value值
String name = getClientName(attributes);
registerClientConfiguration(registry, name, attributes.get("configuration"));
// 注入标注了@FeignClient的接口
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
....
}
registerFeignClient
这里面没啥可说的 就是将被FeignClient标注的接口注入到IoC容器 注入的是
FeignClientFactoryBean
目前这个阶段是初始化,实例化的时候会调用
FeignClientFactoryBean.getObject()
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
....
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
// 校验了 fallback和fallbackFactory
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
// FeignClient并无contextId的配置 这里取的还是name
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();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
// has a default, won't be null. default is true
boolean primary = (Boolean) attributes.get("primary");
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);
}
....
}
FeignClientFactoryBean#getTarget
首先要清楚
FeignClientFactoryBean
的实例里面在上面的步骤已经将它里面的值做了初始化。而在doGetBean时 会调用getObject方法 ,这里如果不清楚,可以看案例关于FactoryBean
其次就是理解
getTarget
做了哪些事情:
- 生成远程代理
- 解析接口模板,例如@GetMapping等
- 处理负载均衡 集成Ribbon
- 包装熔断 继承Hystrix
T getTarget() {
// 这个就是feign的上下文 与spring 容器的上下文隔离开了
FeignContext context = applicationContext.getBean(FeignContext.class);
// 构建一个Feign的Builder
Feign.Builder builder = feign(context);
// 这里判断的是 @FeignClient 的url是否做了配置
if (!StringUtils.hasText(url)) {
if (!name.startsWith("http")) {
url = "http://" + name;
}
else {
url = name;
}
url += cleanPath();
// 这里做负载均衡
return (T) loadBalance(builder, context,
new HardCodedTarget<>(type, name, url));
}
if (StringUtils.hasText(url) && !url.startsWith("http")) {
url = "http://" + 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<>(type, name, url));
}
FeignClientFactoryBean#feign
只是构建了Feign.Builder,其实就是将一些参数记录。
protected Feign.Builder feign(FeignContext context) {
// get方法 = context.getInstance(contextId, type)
// 其中 context就是第一个参数 type是第二个参数
// contextId是FeignClientsRegistrar.registerFeignClient方法里面设置的
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
// 记录日志的 this.logger != null ? this.logger : new Slf4jLogger(type)
Logger logger = loggerFactory.create(type);
// @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
// 配置相关的内容 logger encoder decoder...
configureFeign(context, builder);
return builder;
}
FeignClientFactoryBean#loadBalance
名字虽然叫loadBalance,但是这里并没有做负载均衡,第一行代码
Client client = getOptional(context, Client.class);
,是从IoC容器中获取Client
的Bean,这时触发了IoC容器对该Bean的实例化,代码如下:@Configuration(proxyBeanMethods = false) class DefaultFeignLoadBalancedConfiguration { @Bean @ConditionalOnMissingBean public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory) { return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory, clientFactory); } }
protected T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget target) {
// client = org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient
Client client = getOptional(context, Client.class);
if (client != null) {
// 这里构造的client
builder.client(client);
// targeter = org.springframework.cloud.openfeign.HystrixTargeter
// 即使在项目中没有引入hystrix 也是这个类
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?");
}
HystrixTargeter#target
public T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget target) {
// 没有引入hystrix 时 feign = Feign$Builder 所以就走到了这里
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
// 这里等于 build().newInstance(target),build构建了`ReflectiveFeign`
// 然后这个newInstance是feign.ReflectiveFeign#newInstance
return feign.target(target);
}
feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName()
: factory.getContextId();
SetterFactory setterFactory = getOptional(name, context, SetterFactory.class);
if (setterFactory != null) {
builder.setterFactory(setterFactory);
}
Class> fallback = factory.getFallback();
if (fallback != void.class) {
return targetWithFallback(name, context, target, builder, fallback);
}
Class> fallbackFactory = factory.getFallbackFactory();
if (fallbackFactory != void.class) {
return targetWithFallbackFactory(name, context, target, builder,
fallbackFactory);
}
return feign.target(target);
}
这里的代码跳转是这样的,return feign.target(target);
点进去是feign.Feign.Builder#target(feign.Target
,而方法的内容只有return build().newInstance(target);
,而build方法 除了实例化了几个类,同时将Builder的一些参数做了传递,
这里做了一件事就是把client传过去了。
最后返回了ReflectiveFeign
,所以直接看feign.ReflectiveFeign#newInstance
吧
ReflectiveFeign#newInstance
这里就是通过JDK的动态代理 代理所有的接口了
public T newInstance(Target target) {
// value 是 feign.SynchronousMethodHandler 这一步是模板解析
// 这一步初始化了 SynchronousMethodHandler里面的client
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)));
}
}
// create方法等于new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
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;
}
OpenFeign远程调用
从初始化的流程来看
feign.MethodHandler
是feign.SynchronousMethodHandler
,而java.lang.reflect.InvocationHandler
是feign.ReflectiveFeign.FeignInvocationHandler
,然后在调用方法时 首先要进入的就是feign.ReflectiveFeign.FeignInvocationHandler#invoke
ReflectiveFeign.FeignInvocationHandler#invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 这里时过滤掉Object相关的方法
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();
}
// dispatch是Map (value是SynchronousMethodHandler)
return dispatch.get(method).invoke(args);
}
SynchronousMethodHandler#executeAndDecode
调用链为:
feign.SynchronousMethodHandler#invoke
到feign.SynchronousMethodHandler#executeAndDecode
。
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 {
// 重点在这里 client=org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient
// 在不做特殊配置的情况下,LoadBalancerFeignClient.delegate=feign.Client$Default
// 用的还是HttpURLConnection。
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) {
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);
if (decoder != null)
return decoder.decode(response, metadata.returnType());
CompletableFuture
LoadBalancerFeignClient#execute
到这里能看到一些负载均衡的字眼了 但是追进去会看到好多RxJava的代码,这种反应式代码 只能靠猜。
@Override
public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = getClientConfig(options, clientName);
// FeignLoadBalancer.executeWithLoadBalancer 其实是父类AbstractLoadBalancerAwareClient
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 {
// 最后回调到这里
// org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#execute
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
}).toBlocking()
.single(); // 执行之后可以走到com.netflix.loadbalancer.BaseLoadBalancer#chooseServer
} catch (Exception e) {
Throwable t = e.getCause();
if (t instanceof ClientException) {
throw (ClientException) t;
} else {
throw new ClientException(e);
}
}
}
总结
这里针对开篇提出的问题做一个总结
- OpenFeign的注解是如何被扫描解析的呢?
- 注解标注的接口注入到IoC中的实例是什么?
- OpenFeign的上下文是如何构建的呢?
- 远程通信是如何实现的呢?与RestTemplate有关系么?
OpenFeign的注解是如何被扫描解析的呢?
通过实现接口ImportBeanDefinitionRegistrar
然后注入实例到IoC容器的。
注解标注的接口注入到IoC中的实例是什么?
就是一个JDK的动态代理java.lang.reflect.Proxy
,其中java.lang.reflect.Proxy#h
的值是ReflectiveFeign$FeignInvocationHandler
,如下图所示:
OpenFeign的上下文是如何构建的呢?
public class FeignContext extends NamedContextFactory
远程通信是如何实现的呢?与RestTemplate有关系么?
其实就是这个类org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient
,这里用到了委派模式,delegate再默认情况下是feign.Client$Default
,里面的通信方式是java.net.HttpURLConnection
,可以切换到HttpClient
或者OkHttp3
RestTemplate默认也是基于java.net.HttpURLConnection
封装的一个工具类,Feign中没有直接使用RestTemplate,他们应该属于平级关系,不存在谁使用了谁。
疑问点
如何整合的Ribbon?
LoadBalancerFeignClient
和单独看Ribbon的源码还有联系么?
有关系 但是不太一样了,首先说明一点,Feign 一定要引入spring-cloud-starter-netflix-ribbon
,否则在org.springframework.cloud.openfeign.FeignClientFactoryBean#loadBalance
就会报错。因此,其实分析的Ribbon的源码过程依旧会走一遍,且Feign比Ribbon 早配置。
为啥说不太一样了呢?因为用了Feign就不需要@LoadBalance
和RestTemplate
了,所以请求的时候不会再被LoadBalancerInterceptor
拦截。后面负载均衡那里还会走到Ribbon的com.netflix.loadbalancer.BaseLoadBalancer#chooseServer
。
如何切换请求方式
org.springframework.cloud.openfeign.ribbon.OkHttpFeignLoadBalancedConfiguration
org.springframework.cloud.openfeign.ribbon.HttpClientFeignLoadBalancedConfiguration
org.springframework.cloud.openfeign.loadbalancer.DefaultFeignLoadBalancerConfiguration
Spring的知识点
ImportBeanDefinitionRegistrar
public class TestImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClassName(Hello.class.getName());
registry.registerBeanDefinition("hello", beanDefinition);
}
}
class Hello {
// 省略 getter/setter/toString()
private String name;
}
// 非Spring Boot的环境下,此注解必须有 不然IoC容器中无Hello
@Import(TestImportBeanDefinitionRegistrar.class)
public class Bootstrap {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Bootstrap.class);
System.out.println(context.getBean(Hello.class));
}
}
关于FactoryBean
本案例参考
FeignClientFactoryBean
,将一个接口做出一个代理对象 同时注入到Spring IoC容器中去。
public class TestFactoryBean implements FactoryBean {
@Override
public IHello getObject() {
return (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(), new Class>[]{IHello.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName());
return "hello FactoryBean";
}
});
}
@Override
public Class> getObjectType() {
return IHello.class;
}
}
interface IHello {
String sayS();
Integer sayI();
}
public class TestImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
TestFactoryBean factoryBean = new TestFactoryBean();
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(IHello.class, () -> factoryBean.getObject()).getBeanDefinition();
registry.registerBeanDefinition("hello", beanDefinition);
}
}
@Import(TestImportBeanDefinitionRegistrar.class)
public class Bootstrap {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Bootstrap.class);
IHello bean = context.getBean(IHello.class);
System.out.println(bean);
System.out.println(bean.sayS());
// 这里会报错 因为方法的返回值是int 但是Proxy里面粗暴返回的String
System.out.println(bean.sayI());
}
}