深入理解Springcloud源码篇之Feign源码

编程

Feign在项目中的配置和使用

​ 在Springcloud中使用feign的时候,需要在配置类中加入一个@EnableFeignClients注解。代码如下:

@SpringBootApplication//springboot 启动类

@EnableFeignClients//开启eureka扫描

@EnableDiscoveryClient//开启eureka客户端

public class Application {

public static void main( String[] args ) throws ClassNotFoundException {

SpringApplication.run(Application.class, args);

}

}

配置feign调用客户端

@FeignClient(value = "xxx-server",configuration = FeignConfiguration.class)

public interface ConsumerSmsService extends SMSService{

@RequestMapping(value = "/sms/smsMessage", method = RequestMethod.POST)

RespSMSDto sendSms(ReqSMSDto smsReqDto);

}

经过上面的配置,直接在项目里面注入容器调用接口就可以了。

Feign源码分析

​ 在@EnableFeignClients标签中,import了一个FeignClientsRegistrar类,那么这个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()方法。关于Spring的东西就分析到这里。下面回到正题,分析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);//

}

​ 读过Dubbo源码的同学都知道,当在DubboNamespaceHandler中解析reference标签的时候,传入了一个ReferenceBean对象,把xml中配置的属性都塞到这个对象上,也是装到了beanDefinitionNames中,然后发现ReferenceBean类和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的,指定了urlfeignclient解析,一直跟着代码跟到了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;

}

}

}

Feign源码总结

从读取注解到注入IOC容器,再到编码参数,发起请求,解码结果,整个封装过程都对我们开发带来了极大得便利,此文只是分析了feign带有url参数得解析方式,集成eureka和ribbon的在https://blog.csdn.net/lgq2626/article/details/80481514中做了分析。下面流程图总结下流程:

参考地址

  • https://blog.csdn.net/lgq2626/article/details/80392914

如果大家喜欢我的文章,可以关注个人订阅号。欢迎随时留言、交流。如果想加入微信群的话一起讨论的话,请加管理员简栈文化-小助手(lastpass4u),他会拉你们进群。

以上是 深入理解Springcloud源码篇之Feign源码 的全部内容, 来源链接: utcz.com/z/515098.html

回到顶部