Feign是申明式的 HTTP 客户端。代码中创建一个接口并加上@FeingClient 注解即可使用。其底层封装了 HTTP 客户端构建并发送的复杂逻辑。同时也可以整合注册中心及 Ribbon 为其提供负载均衡能力;通过整合 Histrix/sentinal 实现熔断限流功能。本期主要分享下 Feign 与 SpringCloud 的整合过程,及其底层 HTTP 调用的实现细节。
https://www.springcloud.cc/spring-cloud-greenwich.html#_spring_cloud_openfeign
pom.xml 中添加
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
启动类添加 @EnableFeignClients
注解
@SpringBootApplication(scanBasePackages = {"com.xxx"})
// 指定 FeiginClient 扫描路径
@EnableFeignClients(basePackages = {"com.xxx.biz.feign"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
申明 FeignClient
@FeignClient(name = "inverter-data-service", url = "${feign.property.inverter-data-service.url}", configuration = {xxx})
public interface InverterDataFeign {
@PostMapping(value = "/api/inverter/get_first_date")
FirstDataResponse firstData(@RequestBody String sn, @RequestHeader("token") String token);
}
服务调用
@Service
public class DemoService {
@Autowired
private InverterDataFeign inverterDataFeign;
public void remoteCall(String sn, String token) {
// ...
FirstDataResponse firstDataResponse = inverterDataFeign.firstData(sn, token);
// ...
}
}
@FeignClient
注解是如何使普通的 Java 接口具有 HttpClient 能力的?@FeignClient
并非 @Component
注解,为什么可以像其他Spring 组件一样注入到其他组件中(如 DemoService
)?启动类 Application 申明了注解 @EnableFeignClients
,而@EnableFeignClients
又申明了@Import(FeignClientsRegistrar.class)
, FeignClientsRegistrar
实现了 ImportBeanDefinitionRegistrar
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
/**
* 子类实现
*/
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
当 Spring IOC 容器启动时,会去扫描 @ComponentScan 指定包下所有 @Import 的注解,如果 @Import 的 value 参数是 ImportBeanDefinitionRegistrar 的实现类,就会调用 ImportBeanDefinitionRegistrar#registerBeanDefinitions() 方法,而 FeignClientsRegistrar 就是 ImportBeanDefinitionRegistrar 的实现类;故容器初始化过程中 FeignClientsRegistrar#registerBeanDefinitions() 会被执行,这个方法会做两件事情,1. 注册 @EnableFeignClients 中 configuration 参数指定的默认配置类;2. 扫描并注册子包下被 @FeignClient 注解的 feign 客户端
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 注册默认配置类
registerDefaultConfiguration(metadata, registry);
// 扫描注册 feign 客户端至 spring 容器
registerFeignClients(metadata, registry);
}
}
下面主要看registerFeignClients(metadata, registry)
的实现
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
// 获取 @EnableFeignClients 中所有的参数
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
// 拿到 clients 参数
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
/* 如果未设置 clients 参数,则去子包下扫描所有注解类型为 @FeignClient 的类 */
// 获取扫描器
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
// 设置扫描器的过滤条件,只扫描 @FeignClient 类型
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
// 拿到 @FeignClient 的 basePackages 参数
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
// 执行扫描,构建并添加 BeanDefinition 实体
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
else {
// 如果 clients 参数不为空,则只去注册指定的 FeignClient
for (Class<?> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
/*
遍历 BeanDefinitions(申明 @FeignClient 的类定义)
1. 注册 @FeignClient 中 configuration 参数指定的配置类
2. 注册具体的 FeignClient
*/
for (BeanDefinition candidateComponent : candidateComponents) {
// 查看组件是否是包含注解的 BeanDefinition
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
// 拿到类的注解元数据(包含 class 信息)
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
// 校验,@FeignClient 只能申明在接口上
Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
// 拿到 @FeignClient 的属性数据
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
// 获取客户端名称,依次查看 contextId、value、name、serviceId,只要有值就返回
String name = getClientName(attributes);
// 拿到 configuration 参数,注册配置类
registerClientConfiguration(registry, name, attributes.get("configuration"));
// 注册 FeignClient
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
接着看registerFeignClient(registry, annotationMetadata, attributes)
的逻辑:
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
Map<String, Object> attributes) {
// 拿到接口类名,构建 class 对象
String className = annotationMetadata.getClassName();
Class clazz = ClassUtils.resolveClassName(className, null);
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
? (ConfigurableBeanFactory) registry : null;
String contextId = getContextId(beanFactory, attributes);
String name = getName(attributes);
// 由于 FeignClient 是个接口,业务里没有实现类,所以这里将 feignClient 封装成 FactoryBean,后面对象初始化时,回调其 getObject() 方法,实现动态创建 FeignClient 实现类的 Bean 对象
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
factoryBean.setRefreshableClient(isClientRefreshEnabled());
// 创建 BeanDefinitionBuilder, 会传入一个回调接口 instanceSupplier 的实现,后续在实例化该 BeanDefinition 对应的 Bean 时,会去回调这个接口中实现的方法
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
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();
});
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
definition.setLazyInit(true);
validate(attributes);
// 设置 BD 的一些属性,是否 primary,别名等...
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);
// 注册 FeignClient 对应的 BeanDefinition
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
// 使用 RefreshScope 创建 Request.Options(请求超时时间等配置) BeanDefinition
registerOptionsBeanDefinition(registry, contextId);
}
至此,Spring 容器启动过程中,扫描 FeignClient 并注册到容器的流程已经结束
注册到 Spring 容器中的 BeanDefinition 会存入一个 Map 中(BeanDefinitionMap);所有 BeanDefinition 注册完毕后,Spring 会对其进行初始化,即实例化并存入 Spring 容器;注册 FeignClient 时,FeignClient 被包装成 FeignClientFactoryBean(FactoryBean 实现类)存入 BeanDefinitionMap;故初始化 FeignClientFactoryBean 时,会回调其 getObject() 方法
再来看刚刚注册 FeignClient 的逻辑:
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
Map<String, Object> attributes) {
// 省略前面逻辑...
// 由于 FeignClient 是个接口,业务里没有实现类,所以这里将 feignClient 封装成 FactoryBean,后面对象初始化时,回调其 getObject() 方法,实现动态创建 FeignClient 实现类的 Bean 对象
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
factoryBean.setRefreshableClient(isClientRefreshEnabled());
// 创建 BeanDefinitionBuilder, 会传入一个回调接口 instanceSupplier 的实现,后续在实例化该 BeanDefinition 对应的 Bean 时,会去回调这个接口中实现的方法
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
// 初始化参数
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();
});
// 省略后面逻辑...
}
创建 FeignClient 对象的逻辑均在 FeignClientFactoryBean#getObject() 中:
@Override
public Object getObject() {
return getTarget();
}
/**
* @param the target type of the Feign client
* @return a {@link Feign} client created with the specified data and the context
* information
*/
<T> T getTarget() {
// 获取 FeignContext(FeinContext 是在 FeignAutoConfiguration 中通过 @Bean 注入的)
// 属于 ApplicationContext 的子容器
FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
: applicationContext.getBean(FeignContext.class);
// 从容器中获取 feign builder 对象(该对象也是在 FeignAutoConfiguration 中通过 @Bean 注入的)
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(url)) {
if (url != null && LOG.isWarnEnabled()) {
LOG.warn("The provided URL is empty. Will try picking an instance via load-balancing.");
}
else if (LOG.isDebugEnabled()) {
LOG.debug("URL not provided. Will use LoadBalancer.");
}
if (!name.startsWith("http")) {
url = "http://" + name;
}
else {
url = name;
}
url += cleanPath();
// 负载均衡,需结合 ribbon 使用
return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
}
if (StringUtils.hasText(url) && !url.startsWith("http")) {
url = "http://" + url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof FeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
}
if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
client = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();
}
builder.client(client);
}
// 容器中获取 Targeter 实例(DefaultTargeter)
Targeter targeter = get(context, Targeter.class);
// 获取代理对象
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}
DefaultTargeter#target 会去实例化 ReflectiveFeign, 并调用其 newInstance() 方法实例化代理对象,ReflectiveFeign#newInstance() 逻辑:
public <T> T newInstance(Target<T> target) {
// feign 中调用的方法名 -> SynchronousMethodHandler映射;(SynchronousMethodHandler 是 InvocationHandler 的封装,包含 Feign 请求的配置信息)
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
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)));
}
}
// 创建 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;
}
至此,代理对象创建结束.
当 FeignClient 发起 http 请求时,会从容器中获取对应的代理类,并调用 FeignInvocationHandler#invoke() 方法,其最终实现在 SynchronousMethodHandler#invoke() 中:
public Object invoke(Object[] argv) throws Throwable {
// 解析请求参数,封装 RequestTemplate
RequestTemplate template = buildTemplateFromArgs.create(argv);
// 获取 Request.Options 对象,封装了请求的 connectTimeout/readTimeout 等信息
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;
}
}
}
执行请求的逻辑主要在 executeAndDecode() 方法:
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);
response = response.toBuilder()
.request(request)
.requestTemplate(template)
.build();
} catch (IOException e) {
// ...
}
// ...
}
如果未设置 http 客户端,则使用默认的客户端 Client.Default,即构建 java.net.HttpURLConnection 并发送请求:
public Response execute(Request request, Options options) throws IOException {
// 构建 java.net.HttpURLConnection 并发送请求
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection, request);
}
实例化 FeignClient 时,Feign 为什么要使用 FeignContext 来给每个 FeignClient 创建一个子IOC容器,如果直接使用父容器会有什么问题?