简介
Feign是Spring Cloud Netflix组件中的一个轻量级Restful的HTTP服务客户端,它简化了服务间调用的方式。
Feign是一个声明式的web service客户端.它的出现使开发web service客户端变得更简单.使用Feign只需要创建一个接口加上对应的注解, 比如@FeignClient注解。
Feign是一种声明式、模板化的HTTP客户端。在Spring Cloud中使用Fegin,可以做到使用HTTP请求访问远程服务,就像调用本地方法一样,开发者完全无感知,更感知不到HTTP请求。
Feign的特性:
- 可插拔的注解支持,包含Feign注解
- 支持可插拔的HTTP编码器和解码器
- 支持Hystrix和它的Fallback
- 支持Ribbion的负载均衡
- 支持HTTP请求和响应的压缩
快速入门
引入jar
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
编码
@FeignClient(
url = "/test",
name = "test-api"
)
public interface TestClient {
@PostMapping(
value = {"/create"}
)
void create(@RequestBody User user);
}
启动注解扫描
@Configuration
@EnableFeignClients(basePackages = {"com.test"})
添加注解,启动FeignClient注解扫描,跟我们平时使用ComponentScan的道理都是相似的。
使用
@Resource
private TestClient testClient;
public void create() {
User user = new User();
user.setName("test");
testClient.create(user);
}
完成这些操作之后,我们就可以正常的调用其他服务了,是不是很简单,很容易上手?
那么它到底是如何实现的呢?它都帮我们做了什么事情?接下来的内容才是我们需要了解和掌握的内容。
注册
在我们的业务代码中,我们只是定义了一个添加@FeignClient注解的接口,它是通过JDK的动态代理帮我们创建了接口的代理类,通过代理类完成HTTP的远程调用操作。
在上面的demo中,我们说了,想要调用其他服务,必须添加@EnableFeignClients注解,这个注解就是要扫描我们的代码所有标注了@FeignClient的接口,然后添加到容器当中。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {};
Class<?>[] clients() default {};
}
我们可以自己定义我们要扫描的包路径,加载所有的@FeignClient标识的所有接口。
在这个注解的上面还有一个注解,@Import,这个我想大家应该都知道,是为了导入其他的bean到容器当中,那么Feign的主要加载、扫描、添加注册信息等操作,都是在FeignClientsRegistrar中完成的。
FeignClientsRegistrar
spring框架已经成为了我们日常开发的企业级框架,或者说已经离不开spring的支持了。
FeignClientsRegistrar的处理逻辑就是在调用ApplicationContext的refresh方法的时候,需要初始化一些BeanPostProcessor,在这个过程中,需要处理一些配置类,比如添加了@Configuration注解的类,那么我们使用的Feign开启的时候,就需要这个配置注解。
FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口,那么在容器启动时候就会调用它的registerBeanDefinitions方法,做一些前置处理,通常是把一些bean定义信息注册到容器当中,便于后面初始化这个bean。
现在我们主要看一下FeignClientsRegistrar的registerBeanDefinitions方法
registerFeignClients
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
Set<String> basePackages;
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
。。。省略 //主要是为了获取扫描的包路径,也就是basePackages
for (String basePackage : basePackages) {
//查找添加了@FeignClient注解的接口
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);
//添加FeignClientSpecification的bean定义信息
registerClientConfiguration(registry, name,
attributes.get("configuration"));
//添加FeignClientFactoryBean的bean定义信息
//注册的是FactoryBean信息,那么在初始化这个bean的时候,就会调用到getObject方法,代理的逻辑也是在这里面处理的
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
registerFeignClient
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
//feignclient注解的bean是FeignClientFactoryBean,一个FactoryBean
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
//设置url路径
definition.addPropertyValue("url", getUrl(attributes));
//urlpath
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
//设置服务名
definition.addPropertyValue("name", name);
String contextId = getContextId(attributes);
//beanname的替代名
definition.addPropertyValue("contextId", contextId);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
//设置失败机制
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
//设置bean的别名
String alias = contextId + "FeignClient";
//注解bean定义信息
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
到这里呢, 所有的添加@FeignClient注解的接口已经把bean信息注册到了容器中,等待的就是bean的初始化动作.
bean初始化
在bean的初始化中,由于bean之间的相互依赖,一定会调用FeignClientFactoryBean的getObject方法.
继续跟踪创建代理类的代码
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(type, name, url));
type就是我们定义的Feign接口, name和url就是注解中定义的属性.
接着就会调用到FeignBuilder的target方法
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
调用build方法封装ReflectiveFeign数据,ReflectiveFeign继承了Feign,后面我们调用FeignClient的时候,就是调用它的内部类FeignInvocationHandler的invoke方法
public Feign build() {
Client client = Capability.enrich(this.client, capabilities);
Retryer retryer = Capability.enrich(this.retryer, capabilities);
//Feign调用链路的拦截器
List<RequestInterceptor> requestInterceptors = this.requestInterceptors.stream()
.map(ri -> Capability.enrich(ri, capabilities))
.collect(Collectors.toList());
//... 省略
//设置SynchronousMethodHandler的内部工厂数据
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
//创建ReflectiveFeign
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
接着调用ReflectiveFeign的newInstance方法,Feign接口的代理类的生成就是在这里产生的,每个方法对应的MethodHandler也是在这里注册好的.
public <T> T newInstance(Target<T> target) {
//这里就会遍历元数据信息,生成每个方法名对应的MethodHandler,这里的MethodHandler就是通过SynchronousMethodHandler.Factory
//创建的SynchronousMethodHandler
//MethodHandler是Fegin定义个的方法处理器
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
//method -> MethodHandler
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
//遍历Feign接口中定义的所有方法
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if (Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
//一个Feign接口创建一个对应的InvocationHandler
InvocationHandler handler = factory.create(target, methodToHandler);
//通过jdk动态代理创建代理对象
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
//为默认的方法处理器绑定创建的代理对象
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
InvocationHandler 这里返回的对象还是ReflectiveFeign的内部类FeignInvocationHandler
static class FeignInvocationHandler implements InvocationHandler {
private final Target target;
private final Map<Method, MethodHandler> dispatch;
首先它实现了jdk代理的InvocationHandler 接口,包含2个成员变量,一个目标类, 一个是解析后的方法到MethodHandler的映射关系,便于后面调用的时候,通过dispatch查询对应方法的MethodHandler.
到这里Feign接口的相关注册和初始化动作就结束了.
消费
懒的画图了,从网上复制了一份, 基本上可以完整的概括了FeignClient的调用流程了.
FeignInvocationHandler
了解jdk动态代理的都知道, 想要实现动态代理,首先要实现InvocationHandler接口, 调用的时候就会执行invoke方法,接下来我们仔细看下invoke方法的执行流程.
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//... 省略
//通过初始化好的dispatch,根据对应调用的method查找对应的methodHandler, 从上述的分析过程中,我们看到使用的是
//SynchronousMethodHandler
return dispatch.get(method).invoke(args);
}
SynchronousMethodHandler
追踪到对应的handler上看下
public Object invoke(Object[] argv) throws Throwable {
//封装RequestTemplate 对象, 它包含http相关请求的url,body,method,等等一下参数
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) {
//...
}
//....
continue;
}
}
}
接下来的就是使用Http客户端调用远程服务
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
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);
}
//...
}
http客户端也有很多的实现
其中Okhttp和httpclient是自动装配的, 我们只需要配置一下,就可以启用对应的http客户端,缺省配置的情况下,默认使用ApacheHttpClient.
OkHttp
public feign.Response execute(feign.Request input, feign.Request.Options options)
throws IOException {
okhttp3.OkHttpClient requestScoped;
if (delegate.connectTimeoutMillis() != options.connectTimeoutMillis()
|| delegate.readTimeoutMillis() != options.readTimeoutMillis()
|| delegate.followRedirects() != options.isFollowRedirects()) {
requestScoped = delegate.newBuilder()
.connectTimeout(options.connectTimeoutMillis(), TimeUnit.MILLISECONDS)
.readTimeout(options.readTimeoutMillis(), TimeUnit.MILLISECONDS)
.followRedirects(options.isFollowRedirects())
.build();
} else {
requestScoped = delegate;
}
Request request = toOkHttpRequest(input);
Response response = requestScoped.newCall(request).execute();
return toFeignResponse(response, input).toBuilder().request(input).build();
}
这里就是OkHttp的远程服务调用代码, 没有太多的逻辑.
重试
重试默认使用的Retryer.Default
我们先看下它的默认参数
public Default() {
this(100, SECONDS.toMillis(1), 5);
}
最大尝试次数是5次, 最大时间间隔是1s, 100ms的重试时间间隔, 每次的interval是动态计算下次尝试需要等待的时间
public void continueOrPropagate(RetryableException e) {
//超过最大重试次数直接抛出异常
if (attempt++ >= maxAttempts) {
throw e;
}
long interval;
if (e.retryAfter() != null) {
interval = e.retryAfter().getTime() - currentTimeMillis();
if (interval > maxPeriod) {
interval = maxPeriod;
}
if (interval < 0) {
return;
}
} else {
//根据时间间隔和重试次数计算下次重试需要等待的时间
interval = nextMaxInterval();
}
try {
Thread.sleep(interval);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
throw e;
}
sleptForMillis += interval;
}
Feign的初始化和调用基本原来,先分享到这里,下一期分享一下和ribbon的结合,实现负载均衡.