Open feign源码分析
前沿
最近在做服务拆分和迁移,其中涉及到一些feign接口的迁移,因为涉及到上游比较多,希望可以尽量减少上游的改动量,因此决定调研下open feign
正文
入口EnableFeignClients
@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 {};
}一般来说,入口及时这个注解,注解本身只是一些配置,比如包路径或者指定具体的class等等
需要关注的是@Import(FeignClientsRegistrar.class)这一行,@Import注解用于手动导入一些配置或者主键,是spring boot提供的一种方式,通过这种方式可以将主导权交给开发者
FeignClientsRegistrar
该类实现了ImportBeanDefinitionRegistrar接口,这里看下registerBeanDefinitions方法,做两两件事registerDefaultConfiguration和registerFeignClients
先看registerDefaultConfiguration方法
这里主要是扫描EnableFeignClients的defaultConfiguration属性,并将其注册为bean
接下来看registerFeignClients方法
方法中的第一个if-else块是在找需要注册成bean的feign client class,并将其添加到待注册列表中;优先通过@EnableFeignClients注解的clients属性,如果属性为空,则通过value、basePackages中指定的根路径以及basePackageClasses中指定的类所在的路径作为根路径去扫描根路径下标记了@FeignClient注解的类
接下来就是根据FeignClient属性注册bean,重点看registerFeignClient方法
上面方法就是构造了一个BeanDefinition并注册,BeanDefinition是bean的元数据描述,根据代码,我们可以得知beanFactory是FeignClientFactoryBean,同时在构建BeanDefinition还做了一些准备工作,例如对url和path的处理,这里不具体分析。生成bean式通过BeanFactor也就是FeignClientFactoryBean来实现的
接下来我们看FeignClientFactoryBean
这里getObjectType返回的是bean的类型,其实就是标记了@FeignClient的接口类型,isSingleton方法可以得知bean都是单例的,getObject返回的就是具体的bean,这里是调用了getTarget方法来返回的,接下来看getTarget方法
这里最要看最后两行,通常targeter返回的是DefaultTargeter,具体可以查看FeignAutoConfiguration
接下来查看DefaultTargeter.target方法
实际上是Feign.Builder.target方法
再看build方法
实际上返回了ReflectiveFeign
再看ReflectiveFeign.netInstance方法
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[] {target.type()}, handler)可以看到,这里返回的是一个jdk动态代理对象,需要重点关注的是handler,往上回溯,可以看到handler,是通过factory.create(target, methodToHandler)创建,具体方式是InvocationHandlerFactory.create
实际上返回的是FeignInvocationHandler,实现了InvocationHandler
看下invoke方法,可以看到,实际上跟从dispatch中找到具体method对应的实现方法调用,dispatch需要回溯到ReflectiveFeign.netInstance方法
这里实际是通过反射,然后为每个方法的创建了一个MethodHandler。最终起作用的是methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
nameToHandler = targetToHandlersByName.apply(target);
for循环中,上面的if-else块实际上在解析方法上的一些参数,比如@RequestParam,@RequestBody等然后根据不同参数构造不同的BuildTemplateByResolvingArgs,最终通过factory.create返回一个MethodHandler
具体SynchronousMethodHandler.Factory.create方法
实际返回了一个SynchronousMethodHandler
实际上就是构造一个RequestTemplate,然后通过excuteAndDecode发送请求并解析成具体的返回类型RequestTemplate template = buildTemplateFromArgs.create(argv);就不在详细跟
主要看SynchronousMethodHandler.executeAndDecode方法
Request request = targetRequest(template); 需要关注下,这里可以对RequestTemplate做增强
也就是说我们可以自定义RequestInterceptor来做一些自定义的增强
response = client.execute(request, options);发送http请求
然后剩下的部分就是重试和返回结果解析
至此,源码分析结束
最后更新于