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请求

然后剩下的部分就是重试和返回结果解析

至此,源码分析结束

最后更新于