Feign是Spring Cloud提供的一个声明式的伪Http客户端,它使得调用远程服务就像调用本地服务一样简单,只需要创建一个接口并添加一个注解即可。
Nacos注册中心很好的兼容了Feign,Feign默认集成了Ribbon,所以Nacos下使用Feign默认就实现了负载均衡的效果。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
<version>2.2.10.RELEASEversion>
dependency>
# application.class
@SpringBootApplication
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
# FeignClient
@FeignClient("stores")
public interface StoreClient {
@RequestMapping(method = RequestMethod.GET, value = "/stores")
List<Store> getStores();
@RequestMapping(method = RequestMethod.GET, value = "/stores")
Page<Store> getStores(Pageable pageable);
}
Spring Cloud OpenFeign 默认不为feign一下Bean,但是仍然从应用上下文中查找这些类型的bean来创建feign客户端。
通过Options可以配置连接超时时间和读取超时时间,Options的第一参数是连接的超时时间(ms);第二个是请求处理的超时时间(ms)。
服务消费者:配置类配置(全局配置)
@Configuration
public class FeignClass {
@Bean
public Request.Options options() {
return new Request.Options(5000, 10000);
}
}
服务消费者:配置类配置(局部配置)
public class FeignConfig {
@Bean
public Request.Options options(){
return new Request.Options(5000, 10000);
}
}
@FeignClient(name = "stores", configuration = FeignConfig.class)
public interface StoreClient {
@RequestMapping(method = RequestMethod.GET, value = "/stores")
List getStores();
@RequestMapping(method = RequestMethod.GET, value = "/stores")
Page getStores(Pageable pageable);
}
服务消费者:配置文件配置
# 配置feign的超时时间
feign:
client:
config:
default: # 默认超时时间
stock-service: # 服务提供者的服务名
connectTimeout: 5000
readTimeout: 10000
Feign的底层用的是Ribbon,但是超时时间以Feign配置为准?
服务消费者:配置类配置(全局配置)
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
服务消费者:配置类配置(局部配置)
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
@FeignClient(name = "stores", configuration = FeignConfig.class)
public interface StoreClient {
@RequestMapping(method = RequestMethod.GET, value = "/stores")
List<Store> getStores();
@RequestMapping(method = RequestMethod.GET, value = "/stores")
Page<Store> getStores(Pageable pageable);
}
服务消费者:配置文件配置
feign:
client:
config:
stock-service: #被调用的服务提供者的服务名
loggerLevel:BASIC
OpenFeign日志级别:
<dependency>
<groupId>io.github.openfeigngroupId>
<artifactId>feign-okhttpartifactId>
dependency>
feign.httpclient.enabled=false
feign.okhttp.enabled=true
服务消费者:配置类配置(全局配置)
@Configuration
public class OpenFeignConfig {
/**
* 自定义feign拦截器
* @return
*/
@Bean
public CustomFeignInterceptor customFeignInterceptor() {
return new CustomFeignInterceptor();
}
}
/**
* 自定义feign拦截器
*/
public class CustomFeignInterceptor implements RequestInterceptor {
Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void apply(RequestTemplate requestTemplate) {
//写一些自己的逻辑 带上token 什么之类的
String access_token = UUID.randomUUID().toString();
requestTemplate.header("Authorization",access_token);
logger.info("feign拦截器!");
}
}
服务消费者:配置文件配置
feign:
client:
config:
stock-service: #服务名
requestInterceptors:
- com.gaby.cloud.order.interceptor.feign.CustomFeignInterceptor
OpenFeign是一个远程客户端请求代理,它的基本作用是让开发者能够以面向接口的方式实现远程调用,从而屏蔽底层通信的复杂度,它的具体原理如下图所示:
思考:一个被声明了@FeignClient注解的接口,使用@Autowired进行依赖注入,而最终这个接口能够正常被注入实例
从这个结果来看,可以得出两个结论:
@FeignClient注解是在什么时候解析的?基于我们之前的知识积累,无非就一下几种情况:
在这几个选项种,importBeanDefinitionRegistrar更合适,因为importSelector是批量导入一个bean的string,不适合做Bean的声明。而BeanFactoryPostProcessor是一个bean初始化之前和之后被调用的处理器。
而在我们的FeignClient声明中,并没有Spring相关的注解,自然也不会被Spring容器加载和触发。
那么@FeignClient是在哪里被声明扫描的呢?
在集成FeiginClient时,我们在SpringBoot的main方法中,声明了在一个注解@EnableFeignClient(basePackages="com.zhangmen.api")
。这个注解需要填写一个指定的包名。
看到这里,基本上可以猜出,这个注解必然和@FeignClient
注解的解析又莫大关系。
下面这段代码是@EnableFeignClient
注解的声明,可以看到一个很熟悉的面孔:FeignClientRegister
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients {
}
FeignClientregistrar,主要功能就是针对声明@FeignClient
注解的接口进行扫描和注入到IOC容器。
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
}
果然,这个类实现了ImportBeanDefinitionRegistrar接口。
public interface ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
这个接口用来实现Bean的声明和注册。
简单演示一下ImportBeanDefinitionRegistrar的作用:
在项目中分别创建
- HelloService.java
- MyImportBeanDefinitionRegistrar.java
- EnableMyRegistrar.java
- TestMain
public class HelloService {
}
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClassName(HelloService.class.getName());
registry.registerBeanDefinition("helloService", beanDefinition);
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MyImportBeanDefinitionRegistrar.class})
public @interface EnableFeignClients {
}
@Configuration
@EnableMyRegistrar
public class TeatMain{
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestMain.class);
System.out.println(applicationnContext.getBean(helloService.class));
}
}
通过结果演示可以发现,HelloService这个Bean已经装载到IOC容器。这就是动态装载功能的实现,它相对@Configuration配置注入,会多了很多的灵活性。
registerDefaultConfiguration
方法内部从Springboot
启动类上检查是否有@EnableFeignClients
有该注解的话,则完成Feign框架相关的一些配置内容注册。registerFeignClients
方法内部从classpath
中扫描获得@FeignClient
修饰的类,将类的内容解析为BeanDefinition
,最终通过调用Spring框架中的BeanDefinitionReaderUtils.registerBeanDefinition
将解析处理过的FeignClient
添加到spring容器中。class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//注册@EnableFeignClients中定义defaultConfiguration属性下的类,包装成FeignClientSpecification,注册到Spring容器。
//在@FeignClient中有一个属性:configuration,这个属性是表示各个FeignClient自定义的配置类,后面也会通过调用registerClientConfiguration方法来注册成FeignClientSpecification到容器。
//所以,这里可以完全理解在@EnableFeignClients中配置的是做为兜底的配置,在各个@FeignClient配置的就是自定义的情况。
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
}
这里需要重点分析的就是registerFeignClients(matadata, registry)
方法,这个方法主要是扫描路径下所有的@FeignClient注解,然后进行动态Bean的注入。它最终会调用registerFeignClient方法。
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerFeignClient(registry, annotationMetadata, attributes);
}
//# FeignClientsRegistrar.registerFeignClients
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
//获取@EnableFeignClients注解的元数据
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
//获取@EnableFeignClients注解中的clients属性,可以配置@FeignClient声明的类,如果配置了,则需要扫描并加载。
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
//默认TypeFilter生效,这种模式会查询出许多不符合你要求的class名
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class)); //添加包含过滤的属性@FeignClient。
Set<String> basePackages = getBasePackages(metadata); //从@EnableFeignClients注解中获取basePackages配置。
for (String basePackage : basePackages) {
//scanner.findCandidateComponents(basePackage) 扫描basePackage下的@FeignClient注解声明的接口
candidateComponents.addAll(scanner.findCandidateComponents(basePackage)); //添加到candidateComponents,也就是候选容器中。
}
}
else {//如果配置了clients,则需要添加到candidateComponets中。
for (Class<?> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
//遍历候选容器列表。
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) { //如果属于AnnotatedBeanDefinition实例类型
// verify annotated class is an interface
//得到@FeignClient注解的beanDefinition
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); //获取这个bean的注解元数据
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
//获取元数据属性
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
//获取@FeignClient中配置的服务名称。
String name = getClientName(attributes);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
这个方法就是把FeignClient接口注册到Spring IoC容器
FeignClient是一个接口,那么这里注入到IoC容器中的对象是什么呢?
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName(); //获取FeignClient接口的类全路径
Class clazz = ClassUtils.resolveClassName(className, null); //加载这个接口,得到Class实例
//构建ConfigurableBeanFactory,提供BeanFactory的配置能力
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
? (ConfigurableBeanFactory) registry : null;
//获取contextId
String contextId = getContextId(beanFactory, attributes);
String name = getName(attributes);
//构建一个FeignClient FactoryBean,这个是工厂Bean。
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
//设置工厂Bean的相关属性
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
//BeanDefinitionBuilder是用来构建BeanDefinition对象的建造器
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(clazz, () -> {
//把@FeignClient注解配置中的属性设置到FactoryBean中。
factoryBean.setUrl(getUrl(beanFactory, attributes));
factoryBean.setPath(getPath(beanFactory, attributes));
factoryBean.setDecode404(Boolean
.parseBoolean(String.valueOf(attributes.get("decode404"))));
Object fallback = attributes.get("fallback");
if (fallback != null) {
factoryBean.setFallback(fallback instanceof Class
? (Class<?>) fallback
: ClassUtils.resolveClassName(fallback.toString(), null));
}
Object fallbackFactory = attributes.get("fallbackFactory");
if (fallbackFactory != null) {
factoryBean.setFallbackFactory(fallbackFactory instanceof Class
? (Class<?>) fallbackFactory
: ClassUtils.resolveClassName(fallbackFactory.toString(),
null));
}
return factoryBean.getObject(); //factoryBean.getObject() ,基于工厂bean创造一个bean实例。
});
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); //设置注入模式,采用类型注入
definition.setLazyInit(true); //设置延迟华
validate(attributes);
//从BeanDefinitionBuilder中构建一个BeanDefinition,它用来描述一个bean的实例定义。
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
String[] qualifiers = getQualifiers(attributes);
if (ObjectUtils.isEmpty(qualifiers)) {
qualifiers = new String[] { contextId + "FeignClient" };
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
qualifiers);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); //把BeanDefinition的这个bean定义注册到IOC容器。
}
综上代码分析,其实逻辑很简单
关于上述的逻辑,重点需要关注的是FactoryBean。
首先需要注意的是FactoryBean和BeanFactory是不一样的。FactoryBean是一个工厂Bean,可以生成某一个类型的Bean实例,它最大的一个作用是:可以让我们自定义Bean的创建过程。
而BeanFactory是Spring容器中一个基本类,也是很重要的一个类,在BeanFactory中可以创建和管理Sping容器中的Bean。
下面代码是FactoryBean接口的定义
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
从上面的代码中可以发现在FactoryBean中定义了一个Spring Bean 的很重要的三个特性:是否单例、Bean类型、Bean实例,这也应该是我们关注Spring中的一个Bean最直观的感受。
下面我们来模拟一下@FeiginClient解析以及工程Bean的构建过程。
先定义一个接口,这个接口可以类比我们上面描述的FeignClient
public interface IHelloService {
String say();
}
接着,定义一个工程Bean,这个工厂Bean中主要是针对IHelloService生成动态代理。
public class DefineFactoryBean implements FactoryBean<IHelloService> {
@Override
public IHelloService getObject() {
IHelloService helloService=(IHelloService) Proxy.newProxyInstance(IHelloService.class.getClassLoader(),
new Class<?>[]{IHelloService.class}, (proxy, method, args) -> {
System.out.println("begin execute");
return "Hello FactoryBean";
});
return helloService;
}
@Override
public Class<?> getObjectType() {
return IHelloService.class;
}
}
通过ImportBeanDefinitionRegistrar这个接口,实现动态注入Bean实例
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
DefineFactoryBean factoryBean=new DefineFactoryBean();
BeanDefinitionBuilder definition= BeanDefinitionBuilder.genericBeanDefinition(
IHelloService.class,()-> factoryBean.getObject());
BeanDefinition beanDefinition=definition.getBeanDefinition();
registry.registerBeanDefinition("helloService",beanDefinition);
}
}
声明一个注解,用来表示动态Bean的注入导入。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MyImportBeanDefinitionRegistrar.class)
public @interface EnableMyRegistrar {
}
编写测试类,测试IHelloService这个接口的动态注入
@Configuration
@EnableMyRegistrar
public class TestMain {
public static void main(String[] args) {
ApplicationContext applicationContext=new AnnotationConfigApplicationContext(TestMain.class);
IHelloService is=applicationContext.getBean(IHelloService.class);
System.out.println(is.say());
}
}
运行上述的测试方法,可以看到IHelloService这个接口,被动态代理并注入到IOC容器。
由上述案例可知,SpringIOC容器注入FactoryBean时,会调用FactoryBean的getObject()方法获得bean的实例。因此我们按照这个思路去分析FeiginClientFactoryBean
@Override
public Object getObject() {
return getTarget();
}
构建对象Bean的实例代码如下,这个代码的实现较长,分为几个步骤来看
先看上下文的构建逻辑
<T> T getTarget() {
FeignContext context = beanFactory != null
? beanFactory.getBean(FeignContext.class)
: applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
}
两个关键的对象说明:
FeignContext是全局唯一的上下文,它继承了NameContextFactory,它是用来统一维护feign中各个feign客户端相互隔离的上下文,FeignContext注册到容器是在FeignAutoConfiguration中完成的,代码如下
//FeignAutoConfiguration.java
@Autowired(required = false)
private List configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
在初始化FeignContext时,会把configurations放入到FeignContext中。configuration表示当前被扫描到的所有@FeignClient
Feign.Build是用来构建Feign对象,基于builder实现上下文信息的构建
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(type);
// @formatter:off
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class)); //contract协议,用来实现模版解析(后面再详细分析)
// @formatter:on
configureFeign(context, builder);
applyBuildCustomizers(context, builder);
return builder;
}
从代码中可以看到,feign
方法,主要是针对不同的服务提供者生成Feign的上下文信息,比如logger
、encoder
、decoder
等。因此,从这个分析过程中,我们不难猜测到它的原理结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3BAcitXS-1667540284739)(v2-25538233eaabdaa4361960616d262023_r.jpg)]
父子容器隔离的实现方式如下,当调用get方法时,会从context
中去获取指定type的实例对象。
//FeignContext.java
protected <T> T get(FeignContext context, Class<T> type) {
T instance = context.getInstance(contextId, type);
if (instance == null) {
throw new IllegalStateException(
"No bean found of type " + type + " for " + contextId);
}
return instance;
}
接着,调用NamedContextFactory
中的getInstance
方法
//NamedContextFactory.java
public <T> T getInstance(String name, Class<T> type) {
//根据`name`获取容器上下文
AnnotationConfigApplicationContext context = this.getContext(name);
try {
//再从容器上下文中获取指定类型的bean。
return context.getBean(type);
} catch (NoSuchBeanDefinitionException var5) {
return null;
}
}
getContext
方法根据name
从contexts
容器中获得上下文对象,如果没有,则调用createContext
创建。
protected AnnotationConfigApplicationContext getContext(String name) {
if (!this.contexts.containsKey(name)) {
synchronized(this.contexts) {
if (!this.contexts.containsKey(name)) {
this.contexts.put(name, this.createContext(name));
}
}
}
return (AnnotationConfigApplicationContext)this.contexts.get(name);
}
如果@FeignClient注解中,没有配置url
,也就是不走绝对请求路径,则执行下面这段逻辑。由于我们在@FeignClient注解中使用的是name
,所以需要执行负载均衡策略的分支逻辑。
T getTarget() {
//省略.....
if (!StringUtils.hasText(url)) { //是@FeignClient中的一个属性,可以定义请求的绝对URL
if (LOG.isInfoEnabled()) {
LOG.info("For '" + name
+ "' URL not provided. Will try picking an instance via load-balancing.");
}
if (!name.startsWith("http")) {
url = "http://" + name;
}
else {
url = name;
}
url += cleanPath();
//
return (T) loadBalance(builder, context,
new HardCodedTarget<>(type, name, url));
}
//省略....
}
loadBalance方法的代码实现如下,其中Client
是Spring Boot自动装配的时候实现的,如果替换了其他的http协议框架,则client则对应为配置的协议api。
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
//Feign发送请求以及接受响应的http client,默认是Client.Default的实现,可以修改成OkHttp、HttpClient等。
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client); //针对当前Feign客户端,设置网络通信的client
//targeter表示HystrixTarger实例,因为Feign可以集成Hystrix实现熔断,所以这里会一层包装。
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 or spring-cloud-starter-loadbalancer?");
}
HystrixTarget.target
代码如下,我们没有集成Hystrix,因此不会触发Hystrix相关的处理逻辑。
//HystrixTarget.java
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget<T> target) {
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) { //没有配置Hystrix,则走这部分逻辑
return feign.target(target);
}
//省略....
return feign.target(target);
}
进入到Feign.target
方法,代码如下。
//Feign.java
public <T> T target(Target<T> target) {
return this.build().newInstance(target); //target.HardCodedTarget
}
public Feign build() {
//这里会构建一个LoadBalanceClient
Client client = (Client)Capability.enrich(this.client, this.capabilities);
Retryer retryer = (Retryer)Capability.enrich(this.retryer, this.capabilities);
List<RequestInterceptor> requestInterceptors = (List)this.requestInterceptors.stream().map((ri) -> {
return (RequestInterceptor)Capability.enrich(ri, this.capabilities);
}).collect(Collectors.toList());
//OpenFeign Log配置
Logger logger = (Logger)Capability.enrich(this.logger, this.capabilities);
//模版解析协议(这个接口非常重要:它决定了哪些注解可以标注在接口/接口方法上是有效的,并且提取出有效的信息,组装成为MethodMetadata元信息。)
Contract contract = (Contract)Capability.enrich(this.contract, this.capabilities);
//封装Request请求的 连接超时=默认10s ,读取超时=默认60
Options options = (Options)Capability.enrich(this.options, this.capabilities);
//编码器
Encoder encoder = (Encoder)Capability.enrich(this.encoder, this.capabilities);
//解码器
Decoder decoder = (Decoder)Capability.enrich(this.decoder, this.capabilities);
InvocationHandlerFactory invocationHandlerFactory = (InvocationHandlerFactory)Capability.enrich(this.invocationHandlerFactory, this.capabilities);
QueryMapEncoder queryMapEncoder = (QueryMapEncoder)Capability.enrich(this.queryMapEncoder, this.capabilities);
//synchronousMethodHandlerFactory, 同步方法调用处理器(很重要,后续要用到)
Factory synchronousMethodHandlerFactory = new Factory(client, retryer, requestInterceptors, logger, this.logLevel, this.decode404, this.closeAfterDecode, this.propagationPolicy, this.forceDecoding);
//ParseHandlersByName的作用就是我们传入Target(封装了我们的模拟接口,要访问的域名),返回这个接口下的各个方法,对应的执行HTTP请求需要的一系列信息
ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
build
方法中,返回了一个ReflectiveFeign
的实例对象,先来看ReflectiveFeign
中的newInstance
方法。
public <T> T newInstance(Target<T> target) {
//修饰了@FeignClient注解的接口方法封装成方法处理器,把指定的target进行解析,得到需要处理的方法集合。
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
//定义一个用来保存需要处理的方法的集合
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
//JDK8以后,接口允许默认方法实现,这里是对默认方法进行封装处理。
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
//遍历@FeignClient接口的所有方法
for (Method method : target.type().getMethods()) {
//如果是Object中的方法,则直接跳过
if (method.getDeclaringClass() == Object.class) {
continue;
} else if (Util.isDefault(method)) {
//如果是默认方法,则把该方法绑定一个DefaultMethodHandler。
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
//否则,添加MethodHandler(SynchronousMethodHandler)。
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);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
接口解析也是Feign很重要的一个逻辑,它能把接口声明的属性转化为HTTP通信的协议参数。
执行逻辑RerlectiveFeign.newInstance
public <T> T newInstance(Target<T> target) {
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
//here
}
targetToHandlersByName.apply(target);会解析接口方法上的注解,从而解析出方法粒度的特定的配置信息,然后生产一个SynchronousMethodHandler 然后需要维护一个
public Map<String, MethodHandler> apply(Target target) {
List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.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, queryMapEncoder, target);
} else if (md.bodyIndex() != null) {
buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
} else {
buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target);
}
if (md.isIgnored()) {
result.put(md.configKey(), args -> {
throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
});
} else {
result.put(md.configKey(),
factory.create(target, md, buildTemplate, options, decoder, errorDecoder)); } }
return result;
}
通过上述过程分析,被声明为@FeignClient注解的类,在被注入时,最终会生成一个动态代理对象FeignInvocationHandler。
当触发方法调用时,会被FeignInvocationHandler#invoke拦截,FeignClientFactoryBean在实例化过程中所做的事情如下图所示。
总结来说就几个点:
SynchronousMethodHandler
处理器,用来实现同步远程调用。在Spring启动过程中,把一切的准备工作准备就绪后,就开始执行远程调用。
在前面的分析中,我们知道OpenFeign最终返回的是一个#ReflectiveFeign.FeignInvocationHandler的对象。
那么当客户端发起请求时,会进入到FeignInvocationHandler.invoke方法中,这个大家都知道,它是一个动态代理的实现。
//FeignInvocationHandler.java
@Overridepublic
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();
}
return dispatch.get(method).invoke(args);
}
接着,在invoke方法中,会调用this.dispatch.get(method)).invoke(args)
。this.dispatch.get(method)
会返回一个SynchronousMethodHandler,进行拦截处理。
this.dispatch,其实就是在初始化过程中创建的,private final Map
实例。
这个方法会根据参数生成完成的RequestTemplate对象,这个对象是Http请求的模版,代码如下,代码的实现如下:
@Override
public Object invoke(Object[] argv) throws Throwable { //argv,表示调用方法传递的参数。
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv); //获取配置项,连接超时时间、远程通信数据获取超时时间
Retryer retryer = this.retryer.clone(); //获取重试策略
while (true) {
try {
return executeAndDecode(template, options);
} catch (RetryableException e) {
try {
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
上述代码的执行流程中,需要先构造一个Request对象,然后调用client.execute方法执行网络通信请求,整体实现流程如下。
经过上述的代码,我们已经将RequestTemplate拼装完成,上面的代码中有一个executeAndDecode()
方法,该方法通过RequestTemplate生成Request请求对象,然后利用Http Client获取response,来获取响应信息。
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
Request request = targetRequest(template); //把template转化为请求报文
if (logLevel != Logger.Level.NONE) { //设置日志level
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
//发起请求,此时client是LoadBalanceFeignClient,需要先对服务名称进行解析负载
response = client.execute(request, options); // ensure the request is set. TODO: remove in Feign 12 //获取返回结果
response = response.toBuilder().request(request).requestTemplate(template).build();
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
throw errorExecuting(request, e);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
if (decoder != null) //如果设置了解码器,这需要对响应数据做解码
return decoder.decode(response, metadata.returnType());
CompletableFuture<Object> resultFuture = new CompletableFuture<>();
asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response, metadata.returnType(), elapsedTime);
try {
if (!resultFuture.isDone())
throw new IllegalStateException("Response handling not done");
return resultFuture.join();
} catch (CompletionException e) {
Throwable cause = e.getCause();
if (cause != null)
throw cause;
throw e;
}
}
@Overridepublic
Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url()); //获取请求uri,此时的地址还未被解析。
String clientName = asUri.getHost(); //获取host,实际上就是服务名称
URI uriWithoutHost = cleanUrl(request.url(), clientName);
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(this.delegate, request, uriWithoutHost); //加载客户端的配置信息
IClientConfig requestConfig = getClientConfig(options, clientName); //创建负载均衡客户端(FeignLoadBalancer),执行请求
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
} catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
从上面的代码可以看到,lbClient(clientName) 创建了一个负载均衡的客户端,它实际上就是生成的如下所述的类:
public class FeignLoadBalancer extends AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> {}
OpenFeign的整体通信原理解析,如下图所示。
open-feign原理分析转载 https://zhuanlan.zhihu.com/p/466130934 【 Mic带你学架构】