一、概述
项目中常用的基于http协议的常用请求方式restTemplate 、 okhttp 、 jdk httpurlconnection,那能不能就像调用本地方法一样,进行远程调用?这个就是OpenFeign。
二、OpenFeign要做的事情?
- 参数的解析和装载
- 针对指定的feignClient,生成动态代理
- 针对FeignClient中的方法描述进行解析
- 组装出一个Request对象,发起请求
三、 集成Ribbon 完成负载均衡
之前在Spring Cloud Ribbon中已经讲了,大致流程如下:
1、对有@LoadBalancer注解的RestTemplate进行拦截
2、ILoadBalancer
3、IRule(负载均衡的规则,权重机制(区间算法))
4、IPing(每10s, 去访问一次目标服务的地址,如果服务不可用,则踢出无效服务)
5、ServerList (定时任务,每30s执行一次更新服务列表.)
6、如何自定义负载均衡算法IRule、自定义IPing
四、 FeignClient解析
4.1、spingboot中Bean的动态装载有两种方式
- ImportSelector(在spring自动装配演示过)
- ImportBeanDefinitionRegistrar
这里简单给演示一下ImportBeanDefinitionRegistrar的作用。
定义一个需要被装载到IOC容器中的类HelloService
public class HelloService {
}
定义一个Registrar的实现,定义一个bean,装载到IOC容器
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 EnableMyRegistrar {
}
写一个测试类
@Configuration
@EnableMyRegistrar
public class TestMain {
public static void main(String[] args) {
ApplicationContext applicationContext=new
AnnotationConfigApplicationContext(TestMain.class);
System.out.println(applicationContext.getBean(HelloService.class));
}
}
通过结果演示可以发现,HelloService这个bean 已经装载到了IOC容器。
这就是动态装载的功能实现,它相比于@Configuration配置注入,会多了很多的灵活性
4.2、FeignClientsRegistrar
@EnableFeignClients(basePackages = “org.example.client”)这个注解开启了FeignClient的解析过程。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
}
它用到了一个@Import注解,我们知道Import是用来导入一个配置类的,接下来去看一下FeignClientsRegistrar。
FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar,它是一个动态注入bean的接口,Spring Boot启动的时候,会去调用这个类中的registerBeanDefinitions来实现动态Bean的装载。它的作用类似于ImportSelector。
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware,
EnvironmentAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
}
-
registerDefaultConfiguration 方法内部从 SpringBoot 启动类上检查是否有
@EnableFeignClients, 有该注解的话, 则完成 Feign 框架相关的一些配置内容注册 -
registerFeignClients 方法内部从 classpath 中, 扫描获得 @FeignClient 修饰的类, 将类的内容 解析为 BeanDefinition , 最终通过调用 Spring 框架中的
BeanDefinitionReaderUtils.resgisterBeanDefinition 将解析处理过的 FeignClient BeanDeifinition 添加到 spring 容器中
这里面需要重点分析的就是 registerFeignClients 方法,这个方法主要是扫描类路径下所有的
@FeignClient注解,然后进行动态Bean的注入。它最终会调用 registerFeignClient 方法。
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerFeignClient(registry, annotationMetadata, attributes);
}
在这个方法中,就是去组装BeanDefinition,也就是Bean的定义,然后注册到Spring IOC容器
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object>
attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
//省略代码...
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition,
className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
可以发现,FeignClient被动态注册成了一个FeignClientFactoryBean,FeignClientFactoryBean继承自FactoryBean,它是一个工厂Bean。在Spring中,FactoryBean是一个工厂Bean,用来创建代理Bean。工厂bean 最后返回的实例不是工厂Bean 本身, 而是执行工厂 Bean 的 getObject 逻辑返回的实例。
public static BeanDefinitionBuilder genericBeanDefinition(Class<?> beanClass) {
BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new
GenericBeanDefinition());
builder.beanDefinition.setBeanClass(beanClass);
return builder;
}
简单来说,Spring Cloud FengnClient实际上是利用Spring的代理工厂来生成代理类,FeignClient标注的这个接口,会通过FeignClientFactoryBean.getObject()这个方法获得一个代理对象。
4.3、FeignClientFactoryBean
FeignClientFactoryBean.getObject调用的是getTarget方法,它从applicationContext取出FeignContext,FeignContext继承了NamedContextFactory,它是用来来统一维护feign中各个feign客户端相互隔离的上下文(FeignContext注册到容器是在FeignAutoConfiguration上完成的)。
接着,构建feign.builder,在构建时会向FeignContext获取配置的Encoder,Decoder等各种信息。
配置完Feign.Builder之后,再判断是否需要LoadBalance,如果需要,则通过LoadBalance的方法来设置。实际上他们最终调用的是Target.target()方法。
4.4、动态代理
生成具备负载均衡能力的feign客户端,为feign客户端构建起绑定负载均衡客户端
Client client = (Client)this.getOptional(context, Client.class);
从上下文中获取一个
Client,默认是LoadBalancerFeignClient。
它是在FeignRibbonClientAutoConfiguration这个自动装配类中,通过Import实现的
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
OkHttpFeignLoadBalancedConfiguration.class,
DefaultFeignLoadBalancedConfiguration.class })
protected <T> T loadBalance(Builder builder, FeignContext context,
HardCodedTarget<T> target) {
Client client = (Client)this.getOptional(context, Client.class);
if (client != null) {
builder.client(client);
Targeter targeter = (Targeter)this.get(context, Targeter.class);
return targeter.target(this, builder, context, target);
} else {
throw new IllegalStateException("No Feign Client for loadBalancing
defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
}
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget<T> target) {
return feign.target(target);
}
ReflectiveFeign.newInstance这个方法是用来创建一个动态代理的方法,在生成动态代理之前,会根据Contract协议(协议解析规则,解析接口类的注解信息,解析成内部的MethodHandler的处理方式。从实现的代码中可以看到熟悉的Proxy.newProxyInstance方法产生代理类。而这里需要对每个定义的接口方法进行特定的处理实现,所以这里会出现一个MethodHandler的概念,就是对应方法级别的InvocationHandler。
4.4、接口定义的参数解析
根据FeignClient接口的描述解析出对应的请求数据。
当前Spring Cloud 微服务解决方案中,为了降低学习成本,采用了Spring MVC的部分注解来完成 请求解析,也就是说 ,写客户端请求接口和像写服务端代码一样:客户端和服务端可以通过SDK的方式进行约定,客户端只需要引入服务端发布的SDK API,就可以使用面向接口的编码方式对接服务。
4.5、OpenFeign调用过程
在前面的分析中,我们知道OpenFeign最终返回的是$FeignInvocationHandler的
对象。那么当客户端发起请求时,会进入到FeignInvocationHandler.invoke方法中,它是一个动态代理的实现。
public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable {
if (!"equals".equals(method.getName())) {
if ("hashCode".equals(method.getName())) {
return this.hashCode();
} else {
return "toString".equals(method.getName()) ? this.toString() :
((MethodHandler)this.dispatch.get(method)).invoke(args);
}
} else {
try {
Object otherHandler = args.length > 0 && args[0] != null ?
Proxy.getInvocationHandler(args[0]) : null;
return this.equals(otherHandler);
} catch (IllegalArgumentException var5) {
return false;
}
}
}
而接着,在invoke方法中,会调用 this.dispatch.get(method)).invoke(args) 。
this.dispatch.get(method) 会返回一个SynchronousMethodHandler,进行拦截处理。
这个方法会根据参数生成完成的RequestTemplate对象,这个对象是Http请求的模版。
然后动态生成Request,经过上述的代码,我们已经将restTemplate拼装完成,上面的代码中有一个 executeAndDecode() 方法,该方法通过RequestTemplate生成Request请求对象,然后利用Http Client获取response,来获取响应信息。
最后Client.execute,默认采用JDK的 HttpURLConnection 发起远程调用。这里建议改成okhttp 。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)