什么是feign?
Feign is a Java to HTTP client binder inspired by Retrofit, JAXRS-2.0, and WebSocket. Feign's first goal was reducing the complexity of binding Denominator uniformly to HTTP APIs regardless of ReSTfulness.
Feign 是一个 Java 到 HTTP 客户端绑定器,其灵感来自于 Retrofit、JAXRS-2.0 和 WebSocket。 Feign 的第一个目标是降低将 Denominator 统一绑定到 HTTP API 的复杂性,无论 ReSTativity 如何。
feign能做什么?
feign的项目github仓库上这样描述
Feign works by processing annotations into a templatized request. Arguments are applied to these templates in a straightforward fashion before output. Although Feign is limited to supporting text-based APIs, it dramatically simplifies system aspects such as replaying requests. Furthermore, Feign makes it easy to unit test your conversions knowing this.
Feign 的工作原理是将注释处理为模板化请求。 在输出之前,参数会以简单的方式应用于这些模板。 尽管 Feign 仅限于支持基于文本的 API,但它极大地简化了系统方面,例如重放请求。 此外,知道这一点后,Feign 可以轻松地对您的转换进行单元测试。
总结一下,就是代理http请求,让访问网络api(http\grpc等)像访问内存中的代码方法块一样便捷,把复杂的请求前后处理过程交给feign
feign 的图谱
图片引用来自feign github项目 url:github.com/OpenFeign/f...
contracts
- contracts 的中文释义是
合同
顾名思义就是feign所遵守的协议
clients
- 执行http请求的客户端实现类,默认使用的是
HttpURLConnection
- 可以选择替代的 Apache HTTP、JDK11的 HTTP2等
async clients
- 异步请求客户端,可选Apache HC5、OKHttp等
extras
- 附加组件,提供了对hytrix(熔断器)、slf4j(日志)、mock等的支持
metrics
- 度量工具,提供对Dropwizard 、Micrometer 检测工具的支持
encoders/decoders
- 序列化反序列化支持
feign提供的方法注解
- Feign 注释定义了接口之间的契约以及底层客户端应该如何工作。 Feign的默认合约定义了以下注解:
Annotation | Interface Target | Usage | 中文 |
---|---|---|---|
@RequestLine |
Method | Defines the HttpMethod and UriTemplate for request. Expressions , values wrapped in curly-braces {expression} are resolved using their corresponding @Param annotated parameters. |
定义请求的 HttpMethod 和 UriTemplate。 表达式、包含在大括号 {expression} 中的值使用其相应的 @Param 注解参数进行解析。 |
@Param |
Parameter | Defines a template variable, whose value will be used to resolve the corresponding template Expression , by name provided as annotation value. If value is missing it will try to get the name from bytecode method parameter name (if the code was compiled with -parameters flag). |
定义一个模板变量,其值将用于解析相应的模板表达式,通过作为注释值提供的名称。 如果值丢失,它将尝试从字节码方法参数名称中获取名称(如果代码是使用 -parameters 标志编译的)。 |
@Headers |
Method, Type | Defines a HeaderTemplate ; a variation on a UriTemplate . that uses @Param annotated values to resolve the corresponding Expressions . When used on a Type , the template will be applied to every request. When used on a Method , the template will apply only to the annotated method. |
定义名称-值对(或 POJO)的 Map,以扩展为查询字符串。 |
@QueryMap |
Parameter | Defines a Map of name-value pairs, or POJO, to expand into a query string. |
定义一个 HeaderTemplate; UriTemplate 的变体。 使用@Param注释值来解析相应的表达式。 当用于类型时,模板将应用于每个请求。 当用于方法时,模板将仅应用于带注释的方法。 |
@HeaderMap |
Parameter | Defines a Map of name-value pairs, to expand into Http Headers |
定义名称-值对的映射,以扩展为 Http 标头 |
@Body |
Method | Defines a Template , similar to a UriTemplate and HeaderTemplate , that uses @Param annotated values to resolve the corresponding Expressions . |
定义一个模板,类似于 UriTemplate 和 HeaderTemplate,它使用 @Param 注解的值来解析相应的表达式。 |
特殊的
如果需要访问不同的主机,可以这样使用 @RequestLine 注解
java
import java.net.URI;
/**
* host {@link java.net.URI} feign将使用该主机
*/
@RequestLine("POST /{owner}/api")
void api(URI host, @Param("param") String param,);
默认情况下,RequestLine 模板不对斜杠/字符进行编码。 要更改此行为,请将@RequestLine 上的decodeSlash 属性设置为false。
java
@RequestLine(value = "GET /api/{param}",decodeSlash = false)
public Response api(@Param("param")String param){
return new Response();
}
URL中的
+
在某些遗留系统中,+ 相当于空格。 Feign 采用现代系统的方法,其中 + 符号不应代表空格,并且在查询字符串中找到时显式编码为%2B
。如果需要使用空格,需要使用%20
表示
以上介绍来自于 feign的仓库介绍页
使用他是怎么样的一个过程?
我们来看下feign-core包中的代码执行过程
先拿到bean
以 Spring Cloud OpenFeign 为例子(verion为当前spring官网最新版本 V4.0.4)
从spring的官网中示例可以看到,他是这样用的
Getting Started
java
@SpringBootApplication
@EnableFeignClients
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
}
@FeignClient("name")
static interface NameService {
@RequestMapping("/")
public String getName();
}
}
我们注意到,在springboot 的 application入口上,例子使用了注解 @EnableFeignClients
顾名思义是 启用feignclient的,通过查看@EnableFeignClients
注解的源码
java
@Retention(RetentionPolicy.RUNTIME) //注解级别
@Target(ElementType.TYPE) //类、接口类型的注解
@Documented //java doc
@Import(FeignClientsRegistrar.class) //import FeignClientsRegistrar
public @interface EnableFeignClients {
/**
* basePackages的别名
*/
String[] value() default {};
/**
* 扫描包路径
*/
String[] basePackages() default {};
/**
* 指定扫描类型
*/
Class<?>[] basePackageClasses() default {};
/**
* 默认的feignclient配置,可以指定为自定义配置
*/
Class<?>[] defaultConfiguration() default {};
/**
* 如果不是空的,则禁用类路径,用于标识feiclient列表
* @return list of FeignClient classes
*/
Class<?>[] clients() default {};
}
- 我们发现,他import了FeignClientsRegistrar.class,通过查看FeignClientsRegistrar的源码可以看到
java
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
......
}
其实现了ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware
,他的功能就是把feignclient注入到spring容器中交由spring管理,当我们需要用到feign的api时,我们可以通过 @Autowired
或者@Resource
注解引入,其主要使用的方法是#registerDefaultConfiguration
- registerDefaultConfiguration
java
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//如果类上有@EnableFeignClients注解并且有配置,则执行 registerClientConfiguration 方法,注入client配置
registerDefaultConfiguration(metadata, registry);
//注册client到spring中
registerFeignClients(metadata, registry);
}
- registerFeignClients
java
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
//是否lazy注册
if (String.valueOf(false).equals(
environment.getProperty("spring.cloud.openfeign.lazy-attributes-resolution", String.valueOf(false)))) {
eagerlyRegisterFeignClientBeanDefinition(className, attributes, registry);
}
else {
lazilyRegisterFeignClientBeanDefinition(className, attributes, registry);
}
}
这里我们主要来看看 #eagerlyRegisterFeignClientBeanDefinition
方法
java
private void eagerlyRegisterFeignClientBeanDefinition(String className, Map<String, Object> attributes,
BeanDefinitionRegistry registry) {
//校验参数
validate(attributes);
//构建FactoryBean
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
//配置 factory信息
definition.addPropertyValue("url", getUrl(null, attributes));
definition.addPropertyValue("path", getPath(null, attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
String contextId = getContextId(null, attributes);
definition.addPropertyValue("contextId", contextId);
definition.addPropertyValue("type", className);
definition.addPropertyValue("dismiss404", Boolean.parseBoolean(String.valueOf(attributes.get("dismiss404"))));
//配置feign 的 fallback
Object fallback = attributes.get("fallback");
if (fallback != null) {
definition.addPropertyValue("fallback",
(fallback instanceof Class ? fallback : ClassUtils.resolveClassName(fallback.toString(), null)));
}
Object fallbackFactory = attributes.get("fallbackFactory");
if (fallbackFactory != null) {
definition.addPropertyValue("fallbackFactory", fallbackFactory instanceof Class ? fallbackFactory
: ClassUtils.resolveClassName(fallbackFactory.toString(), null));
}
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
definition.addPropertyValue("refreshableClient", isClientRefreshEnabled());
String[] qualifiers = getQualifiers(attributes);
if (ObjectUtils.isEmpty(qualifiers)) {
qualifiers = new String[] { contextId + "FeignClient" };
}
// 生成 AOT 时可以检索限定符
definition.addPropertyValue("qualifiers", qualifiers);
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
Class<?> type = ClassUtils.resolveClassName(className, null);
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, type);
//primary有默认值,不会为空
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
//执行注入
registerRefreshableBeanDefinition(registry, contextId, Request.Options.class, OptionsFactoryBean.class);
registerRefreshableBeanDefinition(registry, contextId, RefreshableUrl.class, RefreshableUrlFactoryBean.class);
}
- #registerRefreshableBeanDefinition 方法的内容
java
/**
* 该方法向refreshScope 注册bean 定义。
* @paramregistry spring bean定义注册表
* @param contextId feign 客户端名称
* @param beanType bean的类型
* @paramfactoryBeanType指向相关的bean工厂
*/
private void registerRefreshableBeanDefinition(BeanDefinitionRegistry registry, String contextId, Class<?> beanType,
Class<?> factoryBeanType) {
if (isClientRefreshEnabled()) {
String beanName = beanType.getCanonicalName() + "-" + contextId;
BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(factoryBeanType);
definitionBuilder.setScope("refresh");
definitionBuilder.addPropertyValue("contextId", contextId);
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(definitionBuilder.getBeanDefinition(),
beanName);
definitionHolder = ScopedProxyUtils.createScopedProxy(definitionHolder, registry, true);
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
}
}
概括一下就是,启用feign后,在启动阶段会扫描代码里的feiclient注册成bean写入到spring中,从而我们可以通过自动注入拿到bean从而使用bean
@feignClient注解
前面看了feignClient是怎么注入到spring容器中,现在来看看怎么生成feignClient的bean,我们先来看看feignClient注解
java
public @interface FeignClient {
/**
* name的别名
*/
@AliasFor("name")
String value() default "";
/**
* 如果有值作为beanName但不用作于服务id
*/
String contextId() default "";
/**
* 服务id
*/
@AliasFor("value")
String name() default "";
/**
* @return the <code>@Qualifiers</code> value for the feign client.
*/
String[] qualifiers() default {};
/**
* 服务地址 比如说 https://xxx.com
*/
String url() default "";
/**
* 是否处理404
*/
boolean dismiss404() default false;
/**
* client的自定义配置
*/
Class<?>[] configuration() default {};
/**
* 指定错误回调类
*/
Class<?> fallback() default void.class;
/**
* fallback工厂
*/
Class<?> fallbackFactory() default void.class;
/**
* 路径前缀 比如说 path("/v1/api/") -> https://xxx.com/v1/api/
*/
String path() default "";
/**
* 是否标记为主bean,默认为true
*/
boolean primary() default true;
}
feign注解提供了一系列对client的配置,可以在FeignClientsRegistrar#eagerlyRegisterFeignClientBeanDefinition
中看到,构建bean是通过FeignClientFactoryBean
进行构建,我们阿里看看FeignClientFactoryBean
是如何进行构建的
FeignClientFactoryBean.java
java
public class FeignClientFactoryBean
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware, BeanFactoryAware{
......
}
FeignClientFactoryBean类实现 ApplicationContextAware, BeanFactoryAware 接口,
FeignClientFactoryBean 定义了个生产的 feignClient的factory,来看看他的 #getObject()
方法
java
@Override
public Object getObject() {
return getTarget();
}
/**
* @param FeignClient 的类型
* @return 构建一个 Feign
*/
@SuppressWarnings("unchecked")
<T> T getTarget() {
FeignClientFactory feignClientFactory = beanFactory != null ? beanFactory.getBean(FeignClientFactory.class)
: applicationContext.getBean(FeignClientFactory.class);
Feign.Builder builder = feign(feignClientFactory);
if (!StringUtils.hasText(url) && !isUrlAvailableInConfig(contextId)) {
if (LOG.isInfoEnabled()) {
LOG.info("For '" + name + "' URL not provided. Will try picking an instance via load-balancing.");
}
if (!name.startsWith("http://") && !name.startsWith("https://")) {
url = "http://" + name;
}
else {
url = name;
}
url += cleanPath();
return (T) loadBalance(builder, feignClientFactory, new HardCodedTarget<>(type, name, url));
}
if (StringUtils.hasText(url) && !url.startsWith("http://") && !url.startsWith("https://")) {
url = "http://" + url;
}
Client client = getOptional(feignClientFactory, Client.class);
if (client != null) {
if (client instanceof FeignBlockingLoadBalancerClient) {
//处理负载均衡
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
}
if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
// 处理负载均衡
client = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();
}
builder.client(client);
}
// 处理 FeignClientFacty
applyBuildCustomizers(feignClientFactory, builder);
Targeter targeter = get(feignClientFactory, Targeter.class);
return targeter.target(this, builder, feignClientFactory, resolveTarget(feignClientFactory, contextId, url));
}
从源码中可以知道的是,构建了个Feign.Builder,通过builder构建出client,#feign
方法如下
java
protected Feign.Builder feign(FeignClientFactory context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(type);
//配置builder的logger、encode、decode、contract(该处为默认配置)
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));
//对client进行配置,不同的配置(如果配置里有自定义的logger、encode、decode、contract,会对builder进行替换)
configureFeign(context, builder);
return builder;
}
builder从feignContext取,然后配置feignContext中取出的encoder、decoder、contract,再根据feignClient的是否有自定义配置对builder进行配置
java
protected void configureFeign(FeignClientFactory context, Feign.Builder builder) {
FeignClientProperties properties = beanFactory != null ? beanFactory.getBean(FeignClientProperties.class)
: applicationContext.getBean(FeignClientProperties.class);
FeignClientConfigurer feignClientConfigurer = getOptional(context, FeignClientConfigurer.class);
setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());
if (properties != null && inheritParentContext) {
if (properties.isDefaultToProperties()) {
configureUsingConfiguration(context, builder);
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(contextId), builder);
}
else {
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(contextId), builder);
configureUsingConfiguration(context, builder);
}
}
else {
configureUsingConfiguration(context, builder);
}
}
这样一个feignClient的builder就构建完成
总结
概括一下,就是spring启动的时候,会去扫描指定(没指定有默认包路径)包路径下代码,找到被@FeignClient注解的类,构建feignContext并注入client到其中,feignClient必须注入logger、encoder、decoder、contract
FeignClient中方法的执行过程
我们从SpringCloud OpenFeign文档中中看到了这个简单示例
java
@FeignClient(url = "http://localhost:8080")
public interface DemoClient {
@GetMapping("demo")
String getDemo(String param);
}
- 代码里写了个被FeignClient注解的DemoClient接口
- 代码里有个 @GetMapping注解方法getDemo()
- client配置了URL
http://localhost:8080
- @GetMapping配置了接口名
demo
java
@Resource
private DemoClient demoClinet;
......
String demoRes=demoClinet.getDemo("param")
SpringCloud OpenFeign 依赖了OpenFeign,feignClient执行请求我们使用OpenFeignClient和SpringCloud OpenFeig的源码来进行分析
当我们注入Client并调用其方法时,他是怎么执行的,如上代码所示 我们调用了 DemoClient#getDemo(),(这里使用同步feign)通过断点,我们可以看到其实调用了 SynchronousMethodHandler#invoke
,invoke代码如下(以下代码块均以OpenFeign为例):
java
@Override
public Object invoke(Object[] argv) throws Throwable {
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;
}
}
}
RequestTemplate
先来看看 buildTemplateFromArgs.create
是怎么生成RequestTemplate
,create方法如下
java
@Override
public RequestTemplate create(Object[] argv) {
RequestTemplate mutable = RequestTemplate.from(metadata.template());
mutable.feignTarget(target);
if (metadata.urlIndex() != null) {
int urlIndex = metadata.urlIndex();
checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex);
mutable.target(String.valueOf(argv[urlIndex]));
}
Map<String, Object> varBuilder = new LinkedHashMap<String, Object>();
for (Map.Entry<Integer, Collection<String>> entry : metadata.indexToName().entrySet()) {
int i = entry.getKey();
Object value = argv[entry.getKey()];
if (value != null) { // Null values are skipped.
if (indexToExpander.containsKey(i)) {
value = expandElements(indexToExpander.get(i), value);
}
for (String name : entry.getValue()) {
varBuilder.put(name, value);
}
}
}
RequestTemplate template = resolve(argv, mutable, varBuilder);
if (metadata.queryMapIndex() != null) {
// add query map parameters after initial resolve so that they take
// precedence over any predefined values
Object value = argv[metadata.queryMapIndex()];
Map<String, Object> queryMap = toQueryMap(value, metadata.queryMapEncoder());
template = addQueryMapQueryParameters(queryMap, template);
}
if (metadata.headerMapIndex() != null) {
// add header map parameters for a resolution of the user pojo object
Object value = argv[metadata.headerMapIndex()];
Map<String, Object> headerMap = toQueryMap(value, metadata.queryMapEncoder());
template = addHeaderMapHeaders(headerMap, template);
}
return template;
}
构建template
的过程就是把请求的属性加入到request对象中,其中queryMap和headerMap都是通过toQueryMap来的
toQueryMap
java
private Map<String, Object> toQueryMap(Object value, QueryMapEncoder queryMapEncoder) {
if (value instanceof Map) {
return (Map<String, Object>) value;
}
try {
// encode with @QueryMap annotation if exists otherwise with the one from this resolver
return queryMapEncoder != null ? queryMapEncoder.encode(value)
: this.queryMapEncoder.encode(value);
} catch (EncodeException e) {
throw new IllegalStateException(e);
}
}
从toQueryMap
方法中可以看出,对所有的数据都进行了encode,如果没有传递encode对象就对factory中的encode对象来进行encode
encode
接下来我们来看看encode做了什么
encode有2种实现,看名字我们可以看出是对对象
和字段
的处理,将请求参数转换成map。
- BeanQueryMapEncoder#encode()
java
@Override
public Map<String, Object> encode(Object object) throws EncodeException {
if (object == null) {
return Collections.emptyMap();
}
try {
ObjectParamMetadata metadata = getMetadata(object.getClass());
Map<String, Object> propertyNameToValue = new HashMap<String, Object>();
for (PropertyDescriptor pd : metadata.objectProperties) {
Method method = pd.getReadMethod();
Object value = method.invoke(object);
if (value != null && value != object) {
Param alias = method.getAnnotation(Param.class);
String name = alias != null ? alias.value() : pd.getName();
propertyNameToValue.put(name, value);
}
}
return propertyNameToValue;
} catch (IllegalAccessException | IntrospectionException | InvocationTargetException e) {
throw new EncodeException("Failure encoding object into query map", e);
}
}
- FieldQueryMapEncoder#encode()
java
@Override
public Map<String, Object> encode(Object object) throws EncodeException {
if (object == null) {
return Collections.emptyMap();
}
ObjectParamMetadata metadata =
classToMetadata.computeIfAbsent(object.getClass(), ObjectParamMetadata::parseObjectType);
return metadata.objectFields.stream()
.map(field -> this.FieldValuePair(object, field))
.filter(fieldObjectPair -> fieldObjectPair.right.isPresent())
.collect(Collectors.toMap(this::fieldName,
fieldObjectPair -> fieldObjectPair.right.get()));
}
executeAndDecode
从#invoke
方法中可以看到,主要的业务处理是在executeAndDecode
方法中,executeAndDecode
方法代码如下
java
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);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
return responseHandler.handleResponse(
metadata.configKey(), response, metadata.returnType(), elapsedTime);
}
第一行代码中通过#targetRequest(template)
构建了Reuqest,我们再来看看怎么构建Request
targetRequest()
java
Request targetRequest(RequestTemplate template) {
for (RequestInterceptor interceptor : requestInterceptors) {
interceptor.apply(template);
}
return target.apply(template);
}
// apply方法,该段代码截取自Target的HardCodedTarget实现
@Override
public Request apply(RequestTemplate input) {
if (input.url().indexOf("http") != 0) {
input.target(url());
}
return input.request();
}
//RequestTemplate#request()
public Request request() {
if (!this.resolved) {
throw new IllegalStateException("template has not been resolved.");
}
return Request.create(this.method, this.url(), this.headers(), this.body, this);
}
#targetRequest
之中就是将feign拦截器加入到request中去,最后调用RequestTemplate#request
方法去构建Request
Request Object
request包含了
- 请求方法
httpMethod
- 请求地址
url
- 请求头参数
headers
- 请求体
body
- http版本
protocolVersion
默认HTTP/1.1 - requestTemplate
Request构建模板
execute
构建完request对象,通过client的excute提交request从而拿到respone,我们来看看excure是怎么执行的
java
@Override
public Response execute(Request request, Options options) throws IOException {
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection, request);
}
convertAndSend
先是通过 #convertAndSend
方法 拿到HttpURLConnection
,来一探究竟#convertAndSend
做了什么
java
HttpURLConnection convertAndSend(Request request, Options options) throws IOException {
final URL url = new URL(request.url());
final HttpURLConnection connection = this.getConnection(url);
if (connection instanceof HttpsURLConnection) {
HttpsURLConnection sslCon = (HttpsURLConnection) connection;
if (sslContextFactory != null) {
sslCon.setSSLSocketFactory(sslContextFactory);
}
if (hostnameVerifier != null) {
sslCon.setHostnameVerifier(hostnameVerifier);
}
}
connection.setConnectTimeout(options.connectTimeoutMillis());
connection.setReadTimeout(options.readTimeoutMillis());
connection.setAllowUserInteraction(false);
connection.setInstanceFollowRedirects(options.isFollowRedirects());
connection.setRequestMethod(request.httpMethod().name());
Collection<String> contentEncodingValues = request.headers().get(CONTENT_ENCODING);
boolean gzipEncodedRequest = this.isGzip(contentEncodingValues);
boolean deflateEncodedRequest = this.isDeflate(contentEncodingValues);
boolean hasAcceptHeader = false;
Integer contentLength = null;
for (String field : request.headers().keySet()) {
if (field.equalsIgnoreCase("Accept")) {
hasAcceptHeader = true;
}
for (String value : request.headers().get(field)) {
if (field.equals(CONTENT_LENGTH)) {
if (!gzipEncodedRequest && !deflateEncodedRequest) {
contentLength = Integer.valueOf(value);
connection.addRequestProperty(field, value);
}
}
// Avoid add "Accept-encoding" twice or more when "compression" option is enabled
if (field.equals(ACCEPT_ENCODING)) {
connection.addRequestProperty(field, String.join(", ", request.headers().get(field)));
break;
} else {
connection.addRequestProperty(field, value);
}
}
}
// Some servers choke on the default accept string.
if (!hasAcceptHeader) {
connection.addRequestProperty("Accept", "*/*");
}
boolean hasEmptyBody = false;
byte[] body = request.body();
if (body == null && request.httpMethod().isWithBody()) {
body = new byte[0];
hasEmptyBody = true;
}
if (body != null) {
/*
* Ignore disableRequestBuffering flag if the empty body was set, to ensure that internal
* retry logic applies to such requests.
*/
if (disableRequestBuffering && !hasEmptyBody) {
if (contentLength != null) {
connection.setFixedLengthStreamingMode(contentLength);
} else {
connection.setChunkedStreamingMode(8196);
}
}
connection.setDoOutput(true);
OutputStream out = connection.getOutputStream();
if (gzipEncodedRequest) {
out = new GZIPOutputStream(out);
} else if (deflateEncodedRequest) {
out = new DeflaterOutputStream(out);
}
try {
out.write(body);
} finally {
try {
out.close();
} catch (IOException suppressed) { // NOPMD
}
}
}
return connection;
}
从代码中可以看到其逻辑就是
- 从Request对象中获connection参数并设定HttpURLConnection参数
- 建立链接
- 获取链接的输出流OutputStream
- 将Request对象的body写入OutputStream
- 关闭OutputStream
- 返回HttpURLConnection对象
convertResponse
通过 #convertAndSend
方法 拿到HttpURLConnection
,通过#convertResponse
拿到response
java
Response convertResponse(HttpURLConnection connection, Request request) throws IOException {
int status = connection.getResponseCode();
String reason = connection.getResponseMessage();
if (status < 0) {
throw new IOException(format("Invalid status(%s) executing %s %s", status,
connection.getRequestMethod(), connection.getURL()));
}
Map<String, Collection<String>> headers = new TreeMap<>(CASE_INSENSITIVE_ORDER);
for (Map.Entry<String, List<String>> field : connection.getHeaderFields().entrySet()) {
// response message
if (field.getKey() != null) {
headers.put(field.getKey(), field.getValue());
}
}
Integer length = connection.getContentLength();
if (length == -1) {
length = null;
}
InputStream stream;
if (status >= 400) {
stream = connection.getErrorStream();
} else {
stream = connection.getInputStream();
}
if (this.isGzip(headers.get(CONTENT_ENCODING))) {
stream = new GZIPInputStream(stream);
} else if (this.isDeflate(headers.get(CONTENT_ENCODING))) {
stream = new InflaterInputStream(stream);
}
return Response.builder()
.status(status)
.reason(reason)
.headers(headers)
.request(request)
.body(stream, length)
.build();
}
从#convertResponse
方法中可以看到他的执行流程是
- 从HttpURLConnection对象中获取返回内容
- 判断返回结果
- 成功
- code是否正常的httpCode (<400)
- 获取正常返回流数据
- code是否正常的httpCode (>=400)
- 获取错误返回流数据
- 处理流数据
- 构建response
- code是否正常的httpCode (<400)
- 失败
- 抛出异常
- 成功
Response Object
handleResponse
executeAndDecode
方法中,通过client.execute
拿到response
后,在返回去前会调用ResponseHandler#handleResponse
方法处理response
java
public Object handleResponse(String configKey,
Response response,
Type returnType,
long elapsedTime)
throws Exception {
try {
response = logAndRebufferResponseIfNeeded(configKey, response, elapsedTime);
return executionChain.next(
new InvocationContext(configKey, decoder, errorDecoder, dismiss404, closeAfterDecode,
decodeVoid, response, returnType));
} catch (final IOException e) {
if (logLevel != Level.NONE) {
logger.logIOException(configKey, logLevel, e, elapsedTime);
}
throw errorReading(response.request(), response, e);
} catch (Exception e) {
ensureClosed(response.body());
throw e;
}
}
handleResponse方法将respone处理成对象返回,来看看 InvocationContext#proceed()
proceed
java
public Object proceed() throws Exception {
if (returnType == Response.class) {
return disconnectResponseBodyIfNeeded(response);
}
try {
final boolean shouldDecodeResponseBody =
(response.status() >= 200 && response.status() < 300)
|| (response.status() == 404 && dismiss404
&& !isVoidType(returnType));
if (!shouldDecodeResponseBody) {
throw decodeError(configKey, response);
}
if (isVoidType(returnType) && !decodeVoid) {
ensureClosed(response.body());
return null;
}
Class<?> rawType = Types.getRawType(returnType);
if (TypedResponse.class.isAssignableFrom(rawType)) {
Type bodyType = Types.resolveLastTypeParameter(returnType, TypedResponse.class);
return TypedResponse.builder(response)
.body(decode(response, bodyType))
.build();
}
return decode(response, returnType);
} finally {
if (closeAfterDecode) {
ensureClosed(response.body());
}
}
}
#proceed
方法根据返回类型和response的httpCode进行处理,错误的处理方式是throw出对应的错误,如果返回type为void则直接返回null,我们以成功且returnType
不是void
为例,我们可以看到代码中是Types.getRawType(returnType)
拿到rawType
,#getRawType
方法代码如下,通过对type类型的判断获得Class<?>
java
public static Class<?> getRawType(Type type) {
if (type instanceof Class<?>) {
// Type is a normal class.
return (Class<?>) type;
} else if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
// I'm not exactly sure why getRawType() returns Type instead of Class. Neal isn't either but
// suspects some pathological case related to nested classes exists.
Type rawType = parameterizedType.getRawType();
if (!(rawType instanceof Class)) {
throw new IllegalArgumentException();
}
return (Class<?>) rawType;
} else if (type instanceof GenericArrayType) {
Type componentType = ((GenericArrayType) type).getGenericComponentType();
return Array.newInstance(getRawType(componentType), 0).getClass();
} else if (type instanceof TypeVariable) {
// We could use the variable's bounds, but that won't work if there are multiple. Having a raw
// type that's more general than necessary is okay.
return Object.class;
} else if (type instanceof WildcardType) {
return getRawType(((WildcardType) type).getUpperBounds()[0]);
} else {
String className = type == null ? "null" : type.getClass().getName();
throw new IllegalArgumentException("Expected a Class, ParameterizedType, or "
+ "GenericArrayType, but <" + type + "> is of type "
+ className);
}
}
拿到Class<?>
后,通过Class的native方法isAssignableFrom
判断是否feign.TypedResponse
的同族类
- 是
TypedResponse
的同族类- 通过
Types.resolveLastTypeParameter()
拿到bodyType - 对body进行decode成bodyType后通过TypedResponse的builder进行构建TypedResponse对象
- 通过
- 不是
TypedResponse
的同族类- 对body进行decode成returnType类型
decode
查看feign项目core包中的feign.codec.Decoder接口可以看到,有多种方式的实现,core中提供了StringDecoder
、ResponseMappingDecoder
、OptionalDecoder
、StreamDecoder
默认的是使用了
java
public class Default extends StringDecoder {
@Override
public Object decode(Response response, Type type) throws IOException {
if (response.status() == 404 || response.status() == 204)
return Util.emptyValueOf(type);
if (response.body() == null)
return null;
if (byte[].class.equals(type)) {
return Util.toByteArray(response.body().asInputStream());
}
return super.decode(response, type);
}
}
回到SpringCloud OpenFeign
到这,我们回归到SpringCloud OpenFeign中来,在SpringCloud OpenFeign中,其提供了更适合spring中使用的相关代码
LoadBalancer
相比openFeign提供了更多功能,我们可以看到他生成的Client有2种类型
- FeignBlockingLoadBalancerClient
- RetryableFeignBlockingLoadBalancerClient
我们来看看FeignBlockingLoadBalancerClient
,FeignBlockingLoadBalancerClient实现了Client,在execute的时候通过负载均衡或得到了对应的服务url
java
@Override
public Response execute(Request request, Request.Options options) throws IOException {
final URI originalUri = URI.create(request.url());
String serviceId = originalUri.getHost();
Assert.state(serviceId != null, "Request URI does not contain a valid hostname: " + originalUri);
String hint = getHint(serviceId);
DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(
new RequestDataContext(buildRequestData(request), hint));
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
.getSupportedLifecycleProcessors(
loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
RequestDataContext.class, ResponseData.class, ServiceInstance.class);
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);
org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(
instance);
if (instance == null) {
String message = "Load balancer does not contain an instance for the service " + serviceId;
if (LOG.isWarnEnabled()) {
LOG.warn(message);
}
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
.onComplete(new CompletionContext<ResponseData, ServiceInstance, RequestDataContext>(
CompletionContext.Status.DISCARD, lbRequest, lbResponse)));
return Response.builder().request(request).status(HttpStatus.SERVICE_UNAVAILABLE.value())
.body(message, StandardCharsets.UTF_8).build();
}
String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();
Request newRequest = buildRequest(request, reconstructedUrl, instance);
return executeWithLoadBalancerLifecycleProcessing(delegate, options, newRequest, lbRequest, lbResponse,
supportedLifecycleProcessors);
}
decode
从FeignClientsConfiguration
中可以看到默认注入的decode为OpenFeign的OptionalDecoder
java
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder(ObjectProvider<HttpMessageConverterCustomizer> customizers) {
return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(messageConverters, customizers)));
}
OptionalDecoder是这样实现的,我们可以看到实际的decoder是ResponseEntityDecoder,OptionalDecoder工作代码如下:
java
public final class OptionalDecoder implements Decoder {
final Decoder delegate;
public OptionalDecoder(Decoder delegate) {
Objects.requireNonNull(delegate, "Decoder must not be null. ");
this.delegate = delegate;
}
@Override
public Object decode(Response response, Type type) throws IOException {
if (!isOptional(type)) {
return delegate.decode(response, type);
}
if (response.status() == 404 || response.status() == 204) {
return Optional.empty();
}
Type enclosedType = Util.resolveLastTypeParameter(type, Optional.class);
return Optional.ofNullable(delegate.decode(response, enclosedType));
}
static boolean isOptional(Type type) {
if (!(type instanceof ParameterizedType)) {
return false;
}
ParameterizedType parameterizedType = (ParameterizedType) type;
return parameterizedType.getRawType().equals(Optional.class);
}
}
我们再来看看 ResponseEntityDecoder
,具体解析decode的也是通过构造函数携带的decoder
java
public class ResponseEntityDecoder implements Decoder {
private final Decoder decoder;
public ResponseEntityDecoder(Decoder decoder) {
this.decoder = decoder;
}
......
所以SpringCloud OpenFeign默认的decoder是 SpringDecoder
java
//SpringDecoder.java
public SpringDecoder(ObjectFactory<HttpMessageConverters> messageConverters,
ObjectProvider<HttpMessageConverterCustomizer> customizers) {
this.messageConverters = messageConverters;
this.customizers = customizers;
}
//内部类
private final class FeignResponseAdapter implements ClientHttpResponse {
private final Response response;
private FeignResponseAdapter(Response response) {
this.response = response;
}
@Override
public HttpStatus getStatusCode() {
return HttpStatus.valueOf(response.status());
}
@Override
public int getRawStatusCode() {
return response.status();
}
@Override
public String getStatusText() {
return response.reason();
}
@Override
public void close() {
try {
response.body().close();
}
catch (IOException ex) {
// Ignore exception on close...
}
}
@Override
public InputStream getBody() throws IOException {
return response.body().asInputStream();
}
@Override
public HttpHeaders getHeaders() {
return getHttpHeaders(response.headers());
}
}
SpringDecoder的主要业务方法是#decode
java
@Override
public Object decode(final Response response, Type type) throws IOException, FeignException {
if (type instanceof Class || type instanceof ParameterizedType || type instanceof WildcardType) {
List<HttpMessageConverter<?>> converters = messageConverters.getObject().getConverters();
customizers.forEach(customizer -> customizer.accept(converters));
@SuppressWarnings({ "unchecked", "rawtypes" })
HttpMessageConverterExtractor<?> extractor = new HttpMessageConverterExtractor(type, converters);
return extractor.extractData(new FeignResponseAdapter(response));
}
throw new DecodeException(response.status(), "type is not an instance of Class or ParameterizedType: " + type,
response.request());
}
//HttpMessageConverterExtractor.java #extractData
public T extractData(ClientHttpResponse response) throws IOException {
MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
if (responseWrapper.hasMessageBody() && !responseWrapper.hasEmptyMessageBody()) {
MediaType contentType = this.getContentType(responseWrapper);
try {
Iterator var4 = this.messageConverters.iterator();
while(var4.hasNext()) {
HttpMessageConverter<?> messageConverter = (HttpMessageConverter)var4.next();
if (messageConverter instanceof GenericHttpMessageConverter) {
GenericHttpMessageConverter<?> genericMessageConverter = (GenericHttpMessageConverter)messageConverter;
if (genericMessageConverter.canRead(this.responseType, (Class)null, contentType)) {
if (this.logger.isDebugEnabled()) {
ResolvableType resolvableType = ResolvableType.forType(this.responseType);
this.logger.debug("Reading to [" + resolvableType + "]");
}
return genericMessageConverter.read(this.responseType, (Class)null, responseWrapper);
}
}
if (this.responseClass != null && messageConverter.canRead(this.responseClass, contentType)) {
if (this.logger.isDebugEnabled()) {
String className = this.responseClass.getName();
this.logger.debug("Reading to [" + className + "] as "" + contentType + """);
}
return messageConverter.read(this.responseClass, responseWrapper);
}
}
} catch (HttpMessageNotReadableException | IOException var8) {
throw new RestClientException("Error while extracting response for type [" + this.responseType + "] and content type [" + contentType + "]", var8);
}
throw new UnknownContentTypeException(this.responseType, contentType, responseWrapper.getRawStatusCode(), responseWrapper.getStatusText(), responseWrapper.getHeaders(), getResponseBody(responseWrapper));
} else {
return null;
}
}
这里对reponse的解析类似于@RequestBody
注解,将http中的数据序列化回bean(对象通过json、字符串直接转化成String)
总结
feign帮我们完成了从构建http请求到发送http请求再到处理http返回内容的整个过程,我们可以在构建feignClient的时候对encode和decode进行重写以达到我们想要的效果,比如说可以请求进行自定义格式的压缩、对请求自动填充某些数据比如说用户对象的某些属性等以提升不同服务间互相访问的效率和减少交互频率来提升性能。我们也可以自定义logger,来实现打印不同的日志以达到请求链路的染色需求等,还可以自定义http客户端等等。