一、简述
从Spring官网的start示例可以看出,feign的使用仅仅是声明一个接口,然后使用spring mvc或者 JAX-RS的方式给方法打上注解,调用方就可以像使用本地Service那样,依赖并调用了。
了解Spring的同学应该都知道,定义一个接口,没有实现类,想要把这个接口直接注入到Spring容器是不可能的,肯定是用了动态代理生成代理类,并且改变了Spring IOC的行为,将Bean的实例用动态代理类填充了。
那么接下来,我们就从入口,通过源码一步步来解析Feign和Springboot是怎么整合在一起的。
二、FeignClient是怎么实例化到Spring容器的
1、EnableFeignClients 注解
如果我们要开启Feign接口的扫描,会在我们项目启动类上,加上一个@EnableFeignClients的注解,这个大家都会用,但是为什么加了注解,Spring就会去扫描呢?
EnableFeignClients注解内容:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {}
这个注解还有一个Import注解,我们先看Import注解的说明:
/**
* Indicates one or more {@link Configuration @Configuration} classes to import.
*
* <p>Provides functionality equivalent to the {@code <import/>} element in Spring XML.
* Allows for importing {@code @Configuration} classes, {@link ImportSelector} and
* {@link ImportBeanDefinitionRegistrar} implementations, as well as regular component
* classes (as of 4.2; analogous to {@link AnnotationConfigApplicationContext#register}).
*
* <p>{@code @Bean} definitions declared in imported {@code @Configuration} classes should be
* accessed by using {@link org.springframework.beans.factory.annotation.Autowired @Autowired}
* injection. Either the bean itself can be autowired, or the configuration class instance
* declaring the bean can be autowired. The latter approach allows for explicit, IDE-friendly
* navigation between {@code @Configuration} class methods.
*
* <p>May be declared at the class level or as a meta-annotation.
*
* <p>If XML or other non-{@code @Configuration} bean definition resources need to be
* imported, use the {@link ImportResource @ImportResource} annotation instead.
*
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.0
* @see Configuration
* @see ImportSelector
* @see ImportResource
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
它注释其中有一句:
Allows for importing {@code @Configuration} classes, {@link ImportSelector} and {@link ImportBeanDefinitionRegistrar} implementations, as well as regular component classes (as of 4.2; analogous to {@link AnnotationConfigApplicationContext#register}).
说明可以通过@Import注解里,传入ImportSelector.class和ImportBeanDefinitionRegistrar.class的实现类,实现Bean的注册。
如何证明?
在SpringBoot启动过程中,实际上调用了Spring的核心方法AbstractApplicationContext#refresh()。
在refresh方法中,Spring在初始化Bean工厂的时候,会执行ConfigurationClassPostProcessor这个后置处理器,在这个后置处理器中,就会执行ImportSelector或者ImportBeanDefinitionRegistrar实现类的接口实现方法,生成BeanDefinition,后续会被Spring实例化。而且基本我们用到的组件@Enablexxxx,都是应用这个原理,把一个组件的初始化类用@Import来给注解打tag,再将初始化类实现ImportSelector或者ImportBeanDefinitionRegistrar作为value设置到注解中,让Spring自己去调用实例方法,这样就可以用一个自定义注解@Enablexxxx一键开启或者关闭某个组件了,理解了这个,我们应该知道给SpringBoot写一个小组件应该怎么开始了。
这一块的源码不做细讲,因为refresh方法太复杂了,篇幅有限,有兴趣的可以调试 ConfigurationClassPostProcessor这个后置处理器postProcessBeanFactory方法。
2、FeignClientsRegistrar
我们回到ImportBeanDefinitionRegistrar的实现类FeignClientsRegistrar:
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
在registerFeignClients方法中:
首先确定哪些类需要被扫描
如果@EnableFeignClients 中的clients属性配置了有值,其它的属性的配置就不会生效。
然后循环这些包路径,找到对应的有@FeignClient标注的类,执行registerFeignClient方法:
这里最重要的就是:
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
3、FeignClientFactoryBean
FactoryBean是一个接口,其中有一个getObject方法,在执行AbstractBeanFactory#getBean方法时,如果你的name不加上"&"前缀的话,会得到这个getObject方法返回的对象实例。所以我们如果要找Feign的实例,就要去看FeignClientFactoryBean 这个类的getObject方法:
@Overridepublic Object getObject() throws Exception { return getTarget();}
在getTarget()方法中:
会判断@FeignClient注解的url属性有没有值,我们一般是写服务名,这样就会最终生成”http://service-name“的url,然后再执行loadBalance方法。
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
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和Targeter targeter,client变量主要用于设置请求客户端对象,其中客户端的负载均衡就是在这里初始化的,这个留在下一节单独讲。target是用于生成代理对象,如果这个Feign有配置熔断器,也会在这里初始化熔断器相关信息。
4、Targeter,Springboot自动装配
但是Targeter接口有两个实现类,那么在这里,会返回哪一个呢?
这个就要回到我们Springboot的自动装配知识点了。
我们知道,在Springboot启动的过程中,会调用AbstractApplicationContext#refresh()方法,也知道@Import直接的作用,在我们Springboot启动类中,会定义一个注解: @SpringBootApplication,这个注解上面再有@EnableAutoConfiguration注解,@EnableAutoConfiguration注解上面有一个@Import(AutoConfigurationImportSelector.class),
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes =getAttributes(annotationMetadata);
//在这里扫描classpath下所有 META-INF/spring.factories 中的
//org.springframework.boot.autoconfigure.EnableAutoConfiguration 配置,
//并让Spring实例化其配置的所有类
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return StringUtils.toStringArray(configurations);
}
从上面的代码注释可以看出,这个类会扫描classpath下所有 META-INF/spring.factories 中的org.springframework.boot.autoconfigure.EnableAutoConfiguration 配置,并让Spring实例化其配置的所有类。所以org.springframework.cloud.openfeign.FeignAutoConfiguration在Spring容器启动的时候就会被加载到容器并实例化。
使用哪个Targeter,取决于feign.hystrix.HystrixFeign这个类在不在classpath中。
我的工程是引入了这个jar:io.github.openfeign,所以就会使用HystrixTargeter。
进入HystrixTargeter#target方法:
在这个方法里,首先判断@FeignClient注解有没有配置fallback属性,再判断fallbackFactory属性,如果都没有配置,就直接调用feign.target(target)。这里我们假设都没有配置,进入Feign.target()方法:
public <T> T target(Target<T> 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);
}
@Override
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>();
// 将@FeignClient接口中所有的方法存在methodToHandler这个Map中
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if(Util.isDefault(method)) {
//Object默认的方法,会用默认的DefaultMethodHandler
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
//这里通过看上面的build方法,可以知道,value是SynchronousMethodHandler
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
//这里如果没有配置fallBack或者fallBackFactory,会返FeignInvocationHandler
InvocationHandler handler = factory.create(target, methodToHandler);
//对被@FeignClient注解的类利用JDK动态代理产生代理对象实例,并使用FeignInvocationHandler对这个对象进行增强
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
5、FeignInvocationHandler
上面的代码描述了产生代理类的过程,当一个Feign接口被调用时,接下来我们就要看FeignInvocationHandler的invoke方法了:
@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();
}
//这里的dispatch就是我们定义的methodToHandler这个map,直接拿出SynchronousMethodHandler执行invoke方法
return dispatch.get(method).invoke(args);
}
SynchronousMethodHandler#invoke
@Override
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
//这里就是真正执行http请求和负载均衡的地方了
return executeAndDecode(template);
} catch (RetryableException e) {
retryer.continueOrPropagate(e);
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
到此为止,@FeignClient注解的类,怎么会被实例化,并且注入到Spring容器中,已经完成。