Feign 有两个特点:1、声明式REST客户端(伪RPC)2、采用了基于接口的注解
如何使用
在Spring cloud应用中,当我们要使用feign客户端时,一般要做以下三件事情 :
-
使用注解@EnableFeignClients启用feign客户端并设置扫描的路径。
@SpringBootApplication @EnableFeignClients(basePackages = {"com.test"}) public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } }
-
使用注解@FeignClient 定义feign客户端。
@FeignClient(name = "test-service", path = "/test") public interface TestService { @RequestMapping(value = "/echo", method = RequestMethod.GET) TestModel echo(@RequestParam("parameter") String parameter); }
-
使用注解@Autowired使用上面所定义feign的客户端。
@Autowired TestService testService; public void run() { // 这里的使用本地Java API的方式调用远程的Restful接口 TestModel dto = testService.echo("Hello,你好!"); log.info("echo : {}", dto); }
上面的三个步骤,前两个步骤可以理解为定义feign客户端,第三步是使用所定义的feign客户端。通过调试发现,上面第三步所注入的testService是一个代理对象。该对象会代理客户端完成远程服务方法的调用,那么,该代理对象是如何生成的 ?
源码解析
在@EnableFeignClients标签中,import了一个FeignClientsRegistrar类。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
/**
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
* declarations e.g.: {@code @ComponentScan("org.my.pkg")} instead of
* {@code @ComponentScan(basePackages="org.my.pkg")}.
* @return the array of 'basePackages'.
*/
String[] value() default {};
/**
* Base packages to scan for annotated components.
* <p>
* {@link #value()} is an alias for (and mutually exclusive with) this attribute.
* <p>
* Use {@link #basePackageClasses()} for a type-safe alternative to String-based
* package names.
* @return the array of 'basePackages'.
*/
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages()} for specifying the packages to
* scan for annotated components. The package of each class specified will be scanned.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
* @return the array of 'basePackageClasses'.
*/
Class<?>[] basePackageClasses() default {};
/**
* A custom <code>@Configuration</code> for all feign clients. Can contain override
* <code>@Bean</code> definition for the pieces that make up the client, for instance
* {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
*
* @see FeignClientsConfiguration for the defaults
* @return list of default configurations
*/
Class<?>[] defaultConfiguration() default {};
/**
* List of classes annotated with @FeignClient. If not empty, disables classpath
* scanning.
* @return list of FeignClient classes
*/
Class<?>[] clients() default {};
}
我们可以看到这个类继承了ImportBeanDefinitionRegistrar
接口并且实现了registerBeanDefinitions()
方法,
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
...
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
...
}
那么这个FeignClientsRegistrar
中的registerBeanDefinitions()
在什么时候调用的呢?跟着Spring的源码走下去,看过源码的人都会直接看到AbstractApplicationContext
中的refresh()
方法,整体整理一下代码:
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 扫描本项目里面的java文件,把bean对象封装成BeanDefinitiaon对象,然后调用DefaultListableBeanFactory#registerBeanDefinition()方法把beanName放到DefaultListableBeanFactory 的 List<String> beanDefinitionNames 中去
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
postProcessBeanFactory(beanFactory);
// 在这里调用到FeignClientsRegistrar对象的registerBeanDefinitions()方法
invokeBeanFactoryPostProcessors(beanFactory);
//从DefaultListableBeanFactory里面的beanDefinitionNames中找到所有实现了BeanPostProcessor接口的方法,如果有排序进行排序后放到list中
registerBeanPostProcessors(beanFactory);
//Spring的国际化
initMessageSource();
//
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
//
registerListeners();
// Spring的IOC、ID处理。Spring的AOP。事务都是在IOC完成之后调用了BeanPostProcessor#postProcessBeforeInitialization()和postProcessBeforeInitialization()方法,AOP(事务)就是在这里处理的
finishBeanFactoryInitialization(beanFactory);
// 执行完之后调用实现了所有LifecycleProcessor接口的类的onRefresh()方法,同时调用所有观察了ApplicationEvent接口的事件(观察者模式)
finishRefresh();
}
catch (BeansException ex) {
// 找到所有实现了DisposableBean接口的方法,调用了destroy()方法,这就是bean的销毁
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
throw ex;
}
finally {
resetCommonCaches();
}
}
}
根据上面整理的代码发现,FeignClientsRegistrar
中的registerBeanDefinitions()
方法是在扫描完bean之后,只设置了beanname的情况下, 并没有进行IOC注册的时候调用的,这就是Spring动态扩展Bean,实现BeanDefinitionRegistryPostProcessor接口的所有方法也会在这里调用下postProcessBeanDefinitionRegistry()方法。
主要逻辑在FeignClientsRegistrar
中的registerBeanDefinitions()
方法:
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);//扫描EnableFeignClients标签里配置的信息,注册到beanDefinitionNames中。
registerFeignClients(metadata, registry);
}
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
//省略代码...根据EnableFeignClients配置的basePackages找到包下所有FeignClient注解的类,Spring的Commponet也是这么干的
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
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<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
/**
* 关键地方:Feign子容器概念:
* 在注入FeignAutoConfiguration类的时候,注入了一个FeignContext对象,这个就是Feign的子容器。
* 这里面装了List<FeignClientSpecification>对象,FeignClientSpecification对象的实质就是在@feignClient上配置的name为key,value为configuration对象的值
* 比如feignclient 这样配置的@FeignClient(url="https://api.weixin.qq.com",name="${usercenter.name}", configuration = UserCenterFeignConfiguration.class, primary= false)
* 那么在FeignContext中就会出现一个FeignClientSpecification{name='sms-server', configuration=[class com.jfbank.sms.configuration.FeignConfiguration]}这样的数据。
* 这个地方比较关键,主要是因为后期对feign客户端的编码解码会用到自定义的类
*/
//这个方法就是在ioc容器中塞入一个FeignClientSpecification对象,从而构建FeignContext子容器。
registerClientConfiguration(registry, name,
attributes.get("configuration"));
//重点分析这个
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);//对FeignClientFactoryBean对象生成一个BeanDefinition对象
...读取配置
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 });
//注册到beanDefinitionNames中对象
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);//
}
FeignClientFactoryBean
都实现了FactoryBean
的接口,并且里面都有getObject()
和getObjectType()
方法。当接口调用到这个feign客户端的时候,会从IOC中读取这个FeignClientFactoryBean
并且调用getObject
方法。下面就是分析getObject
方法:
@Override
public Object getObject() throws Exception {
FeignContext context = applicationContext.getBean(FeignContext.class);
//从上文中的子容器中获取编码器,解码器等自定义类,然后封装一个Feign.Builder类
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {//当@FeignClient没有配置url的时候
String url;
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
return loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));//集成了ribbon客户端负载均衡
}
//当@FeignClient配置了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 lod balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient)client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}
所以如果配置了url那么就构建RestTemple发送请求,否则就使用Ribbon发送请求。
首先看配置了url的,指定了url的feignclient
解析,一直跟着代码跟到了Feign.Builder
中的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);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
}
直接看ReflectiveFeign
的newInstance()
方法:
//ReflectiveFeign#newInstance()
public <T> T newInstance(Target<T> target) {
//动态代理的handler类目前穿进来的是ParseHandlersByName类,所以这里要看ParseHandlersByName#apply()直接看下一个方法
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if(Util.isDefault(method)) {//默认方法会走到这里,比如toString(),hashCode()等方法
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {//这里才是装配的调用类,上文分析到计息的handler是SynchronousMethodHandler#invoke()
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);//jdk动态代理
for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
//ParseHandlersByName#apply类,构建动态代理的handler
public Map<String, MethodHandler> apply(Target key) {
List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
for (MethodMetadata md : metadata) {
BuildTemplateByResolvingArgs buildTemplate;
if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder);//通过自定义的encoder去解析参数
} else if (md.bodyIndex() != null) {
buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder);//通过自定义的encoder去解析参数
} else {
buildTemplate = new BuildTemplateByResolvingArgs(md);
}
//创建handler,再看Factory#create()方法,下一个方法
result.put(md.configKey(),
factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
}
return result;
}
//Factory#create(),构建一个SynchronousMethodHandler去处理请求,调用invoke方法
public MethodHandler create(Target<?> target, MethodMetadata md,
RequestTemplate.Factory buildTemplateFromArgs,
Options options, Decoder decoder, ErrorDecoder errorDecoder) {
return new SynchronousMethodHandler(target, client, retryer, requestInterceptors, logger,
logLevel, md, buildTemplateFromArgs, options, decoder,
errorDecoder, decode404);
}
//SynchronousMethodHandler#invoke()方法:实际调用的方法
//@Override
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);//构建requestTemplate对象
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template);//下面不分析了,就是执行execute方法并且解码饭后返回值
} catch (RetryableException e) {
retryer.continueOrPropagate(e);
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
ribbon和feign的集成请求方式:比较简单的代码就直接过了,直接看重点LoadBalancerCommand
的submit()
public Observable<T> submit(final ServerOperation<T> operation) {
...省略代码...
// Use the load balancer
Observable<T> o =
//选择server,和feign默认的选择策略是ZoneAwareLoadBalancer
(server == null ? selectServer() : Observable.just(server))
.concatMap(new Func1<Server, Observable<T>>() {
@Override
// Called for each server being selected
public Observable<T> call(Server server) {
context.setServer(server);
final ServerStats stats = loadBalancerContext.getServerStats(server);
// Called for each attempt and retry
Observable<T> o = Observable
.just(server)
.concatMap(new Func1<Server, Observable<T>>() {
@Override
public Observable<T> call(final Server server) {
context.incAttemptCount();
loadBalancerContext.noteOpenConnection(stats);//做一些记录,包括把服务器的连接数+1,设置请求的开始时间,方便上文中讲到的策略选择
if (listenerInvoker != null) {
try {
listenerInvoker.onStartWithServer(context.toExecutionInfo());
} catch (AbortExecutionException e) {
return Observable.error(e);
}
}
final Stopwatch tracer = loadBalancerContext.getExecuteTracer().start();
//在这个call里面调用服务,调用到了FeignLoadBalancer#execute()/RetryableFeignLoadBalancer#execute(重试)去发起请求,并且返回值
return operation.call(server).doOnEach(new Observer<T>() {
...省略代码...//处理返回值,包括把服务器连接数-1,状态变化,统计请求时间,方便策略选择
});
}
});
if (maxRetrysSame > 0)
o = o.retry(retryPolicy(maxRetrysSame, true));
return o;
}
});
...省略代码...
}
Feign源码总结
从读取注解到注入IOC容器,再到编码参数,发起请求,解码结果,整个封装过程都对我们开发带来了极大得便利。下面流程图总结下流程: