文章目錄
1、什麼是Feign
Feign 是一個聲明式 HTTP 調用客戶端,Feign 的目標是使編寫Java HTTP客戶端更加容易。Feign 的聲明編程調用思想來自 Retrofit ,Retrofit 在 Android 端十分流行,Feign 借鑑了 Retrofit 的編程理念。
Feign 支持以下幾種 HTTP 客戶端 Apache Httpclient , Square OkHttp , HttpURLConnection。
2、什麼是 Spring Cloud OpenFeign
Spring Cloud 中服務與服務之間的調用都是通過 HTTP 完成的,而 Spring Cloud OpenFeign 就是基於 Fegin 的封裝,與Spring Boot 結合實現開箱即用,來實現服務之間內部調用的客戶端。
3、Feign 實現原理
以Fegin官方提供的示例(https://github.com/OpenFeign/feign),來解析其實現的原理。如下,獲取指定 Github 倉庫的所有貢獻者:
/**
* 定義聲明式接口
*/
interface GitHub {
/**
* 聲明調用方式 GET 和 調用地址,獲取指定倉庫貢獻者列表
*/
@RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repository);
class Contributor {
String login;
int contributions;
}
}
public static void main(String[] args) {
//Feign客戶端初始化
GitHub github = Feign.builder()
.decoder(new GsonDecoder())
.logLevel(Logger.Level.FULL)
.target(GitHub.class, "https://api.github.com");
//獲取並打印feign的貢獻者列表。
List<GitHub.Contributor> contributors = github.contributors("OpenFeign", "feign");
contributors.forEach(contributor -> System.out.println(contributor.login + " (" + contributor.contributions + ")"));
}
從示例中可以看到,Feign客戶端初始化包括三個部分,Feign 默認配置初始化、設置自定義配置、生成代理對象。
3.1、Feign.builder() 初始化構造器
根據默認值初始化 Feign 構造器
public static Builder builder() {
return new Builder();
}
public static class Builder {
//請求攔截器,攔截每一個請求,添加或修改請求屬性
private final List<RequestInterceptor> requestInterceptors =
new ArrayList<RequestInterceptor>();
//日誌級別,默認關閉
private Logger.Level logLevel = Logger.Level.NONE;
//定義接口上有效的註釋和值。
private Contract contract = new Contract.Default();
//客戶端實例化。空參數表示使用默認值。
private Client client = new Client.Default(null, null);
//默認重試規則
private Retryer retryer = new Retryer.Default();
private Logger logger = new NoOpLogger();
//編碼器
private Encoder encoder = new Encoder.Default();
//解碼器
private Decoder decoder = new Decoder.Default();
//生成查詢參數,eg: "/uri?name={name}&number={number}"
private QueryMapEncoder queryMapEncoder = new FieldQueryMapEncoder();
//錯誤解碼處理器
private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
//控制請求的參數,比如:超時時間
private Options options = new Options();
//反射控制工廠
private InvocationHandlerFactory invocationHandlerFactory =
new InvocationHandlerFactory.Default();
private boolean decode404;
private boolean closeAfterDecode = true;
private ExceptionPropagationPolicy propagationPolicy = NONE;
。。。。。。省略。。。。。。。。
}
3.2、Feign.target() 生成代理對象
public <T> T target(Class<T> apiType, String url) {
return target(new HardCodedTarget<T>(apiType, url));
}
public <T> T target(Target<T> target) {
//調用JDK動態代理生成接口代理類
return build().newInstance(target);
}
public Feign build() {
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
// 注入依賴配置項
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
public class ReflectiveFeign extends Feign {
@Override
public <T> T newInstance(Target<T> target) {
//根據 Contract 約定,解析接口方法上的註解,建立方法名與對應處理器的映射關係
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)));
}
}
// 使用JDK動態代理爲接口生成代理對象,實際業務處理交給 InvocationHandler 處理,實質就是調用相應的 MethodHandler
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;
}
}
// InvocationHandler 實現代理類 ,Map<Method, MethodHandler>
static class FeignInvocationHandler implements InvocationHandler {
private final Target target;
//根據接口註解生成的方法對象與方法處理器的映射對象
private final Map<Method, MethodHandler> dispatch;
FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
}
@Override
public 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();
}
//根據調用的方法從 Map<Method, MethodHandler> 中獲取對應的 MethodHandler,實現http的調用
return dispatch.get(method).invoke(args);
}
}
target 方法通過使用JDK動態代理 Proxy.newProxyInstance,爲FeginClient接口生成代理對象處理器 FeignInvocationHandler。最後通過調用 FeignInvocationHandler 中的 invoke 方法,
從之前解析出的 methodToHandler 映射關係中獲取真正的代理實現類,執行具體的業務邏輯。
3.3、如何解析 FeginClient 接口中定義的方法
那麼是如何解析 Github.contributors 方法的註解呢。 Feign中提供一個 Contract 解析契約(協議),如果沒有指定,默認使用 BaseContract 中的實現。
3.3.1、默認Contract 實現
abstract class BaseContract implements Contract {
/**
* @param targetType {@link feign.Target#type() type} of the Feign interface.
* @see #parseAndValidateMetadata(Class)
*/
@Override
public List<MethodMetadata> parseAndValidateMetadata(Class<?> targetType) {
//不支持參數化類型
checkState(targetType.getTypeParameters().length == 0, "Parameterized types unsupported: %s",
targetType.getSimpleName());
//只支持單繼承接口
checkState(targetType.getInterfaces().length <= 1, "Only single inheritance supported: %s",
targetType.getSimpleName());
if (targetType.getInterfaces().length == 1) {
checkState(targetType.getInterfaces()[0].getInterfaces().length == 0,
"Only single-level inheritance supported: %s",
targetType.getSimpleName());
}
Map<String, MethodMetadata> result = new LinkedHashMap<String, MethodMetadata>();
for (Method method : targetType.getMethods()) {
if (method.getDeclaringClass() == Object.class ||
(method.getModifiers() & Modifier.STATIC) != 0 ||
Util.isDefault(method)) {
continue;
}
MethodMetadata metadata = parseAndValidateMetadata(targetType, method);
checkState(!result.containsKey(metadata.configKey()), "Overrides unsupported: %s",
metadata.configKey());
result.put(metadata.configKey(), metadata);
}
return new ArrayList<>(result.values());
}
/**
* Called indirectly by {@link #parseAndValidateMetadata(Class)}.
*/
protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
MethodMetadata data = new MethodMetadata();
data.targetType(targetType);
data.method(method);
data.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType()));
data.configKey(Feign.configKey(targetType, method));
//類註解解析
if (targetType.getInterfaces().length == 1) {
processAnnotationOnClass(data, targetType.getInterfaces()[0]);
}
processAnnotationOnClass(data, targetType);
//方法註解解析
for (Annotation methodAnnotation : method.getAnnotations()) {
processAnnotationOnMethod(data, methodAnnotation, method);
}
if (data.isIgnored()) {
return data;
}
checkState(data.template().method() != null,
"Method %s not annotated with HTTP method type (ex. GET, POST)",
data.configKey());
Class<?>[] parameterTypes = method.getParameterTypes();
Type[] genericParameterTypes = method.getGenericParameterTypes();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
int count = parameterAnnotations.length;
for (int i = 0; i < count; i++) {
boolean isHttpAnnotation = false;
if (parameterAnnotations[i] != null) {
isHttpAnnotation = processAnnotationsOnParameter(data, parameterAnnotations[i], i);
}
if (isHttpAnnotation) {
data.ignoreParamater(i);
}
if (parameterTypes[i] == URI.class) {
data.urlIndex(i);
} else if (!isHttpAnnotation && parameterTypes[i] != Request.Options.class) {
if (data.isAlreadyProcessed(i)) {
checkState(data.formParams().isEmpty() || data.bodyIndex() == null,
"Body parameters cannot be used with form parameters.");
} else {
checkState(data.formParams().isEmpty(),
"Body parameters cannot be used with form parameters.");
checkState(data.bodyIndex() == null, "Method has too many Body parameters: %s", method);
data.bodyIndex(i);
data.bodyType(Types.resolve(targetType, targetType, genericParameterTypes[i]));
}
}
}
if (data.headerMapIndex() != null) {
checkMapString("HeaderMap", parameterTypes[data.headerMapIndex()],
genericParameterTypes[data.headerMapIndex()]);
}
if (data.queryMapIndex() != null) {
if (Map.class.isAssignableFrom(parameterTypes[data.queryMapIndex()])) {
checkMapKeys("QueryMap", genericParameterTypes[data.queryMapIndex()]);
}
}
return data;
}
。。。。。。。。。。。。。。省略。。。。。。。。。。。。
}
3.3.2、SpringMvcContract 基於Spring MVC的協議規範
Spring Cloud 微服務解決方案中,爲了降低學習成本,採用了Spring MVC的部分註解來完成請求協議解析,客戶端和服務端可以通過SDK的方式進行約定,客戶端只需要引入服務端發佈的 API,就可以使用面向接口的編碼方式對接服務。
Spring Cloud 沒有基於Spring MVC 全部註解來做Feign 客戶端註解協議解析,有一些註解並不支持,如 @GetMapping,@PutMapping 等,僅支持使用 @RequestMapping 等。
基於 Spring MVC 註解規範解析類上的註解
@Override
protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
if (clz.getInterfaces().length == 0) {
RequestMapping classAnnotation = findMergedAnnotation(clz,
RequestMapping.class);
if (classAnnotation != null) {
// Prepend path from class annotation if specified
if (classAnnotation.value().length > 0) {
String pathValue = emptyToNull(classAnnotation.value()[0]);
pathValue = resolve(pathValue);
if (!pathValue.startsWith("/")) {
pathValue = "/" + pathValue;
}
data.template().uri(pathValue);
}
}
}
}
基於 Spring MVC 註解規範解析方法上的註解
@Override
protected void processAnnotationOnMethod(MethodMetadata data,
Annotation methodAnnotation, Method method) {
if (!RequestMapping.class.isInstance(methodAnnotation) && !methodAnnotation
.annotationType().isAnnotationPresent(RequestMapping.class)) {
return;
}
RequestMapping methodMapping = findMergedAnnotation(method, RequestMapping.class);
// HTTP Method
RequestMethod[] methods = methodMapping.method();
if (methods.length == 0) {
methods = new RequestMethod[] { RequestMethod.GET };
}
checkOne(method, methods, "method");
data.template().method(Request.HttpMethod.valueOf(methods[0].name()));
// path
checkAtMostOne(method, methodMapping.value(), "value");
if (methodMapping.value().length > 0) {
String pathValue = emptyToNull(methodMapping.value()[0]);
if (pathValue != null) {
pathValue = resolve(pathValue);
// Append path from @RequestMapping if value is present on method
if (!pathValue.startsWith("/") && !data.template().path().endsWith("/")) {
pathValue = "/" + pathValue;
}
data.template().uri(pathValue, true);
}
}
// produces
parseProduces(data, method, methodMapping);
// consumes
parseConsumes(data, method, methodMapping);
// headers
parseHeaders(data, method, methodMapping);
data.indexToExpander(new LinkedHashMap<Integer, Param.Expander>());
}
3.2、MethodHandler
MethodHandler 有兩個實現類:DefaultMethodHandler 和 SynchronousMethodHandler,feign 最終的http請求就是在同步方法處理器(SynchronousMethodHandler)中完成的。
MethodHandler 請求處理邏輯
@Override
public Object invoke(Object[] argv) throws Throwable {
//請求參數封裝爲 RequestTemplate
RequestTemplate template = buildTemplateFromArgs.create(argv);
//重試處理器
Retryer retryer = this.retryer.clone();
// 重試邏輯
while (true) {
try {
//請求返回
return executeAndDecode(template);
} 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;
}
}
}
Object executeAndDecode(RequestTemplate template) throws Throwable {
// 根據不同參數構建請求模板,並執行請求攔截器生成最終Request
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
//返回響應信息
Response response;
long start = System.nanoTime();
try {
//通過 client 完成請求,Apache Httpclient、Square OkHttp 或 HttpURLConnection 。默認是 HttpURLConnection。
response = client.execute(request, options);
} 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);
boolean shouldClose = true;
try {
if (logLevel != Logger.Level.NONE) {
response =
logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
}
if (Response.class == metadata.returnType()) {
if (response.body() == null) {
return response;
}
if (response.body().length() == null ||
response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
shouldClose = false;
return response;
}
// Ensure the response body is disconnected
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
return response.toBuilder().body(bodyData).build();
}
//處理正常返回
if (response.status() >= 200 && response.status() < 300) {
if (void.class == metadata.returnType()) {
return null;
} else {
//解碼操作
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
}
//異常處理
} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
} else {
throw errorDecoder.decode(metadata.configKey(), response);
}
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
}
throw errorReading(request, response, e);
} finally {
if (shouldClose) {
ensureClosed(response.body());
}
}
}
//解碼操作
Object decode(Response response) throws Throwable {
try {
return decoder.decode(response, metadata.returnType());
} catch (FeignException e) {
throw e;
} catch (RuntimeException e) {
throw new DecodeException(e.getMessage(), e);
}
}
3.3、最終的HTTP請求
feign 中默認的http請求,使用 HttpURLConnection 實現,每一次請求都會創建一個連接。這裏可以使用連接池來優化該問題,配置中可以使用 HttpClient 或者 OKHttp。
class Default implements Client {
private final SSLSocketFactory sslContextFactory;
private final HostnameVerifier hostnameVerifier;
/**
* Null parameters imply platform defaults.
*/
public Default(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier) {
this.sslContextFactory = sslContextFactory;
this.hostnameVerifier = hostnameVerifier;
}
@Override
public Response execute(Request request, Options options) throws IOException {
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection, request);
}
4、Spring Cloud 中的 OpenFeign
在spring cloud中,如果要啓用Fegin,需要在啓動類中添加註解 @EnableFeignClients。從該註解源碼中我們可以看到,引入了類 @Import(FeignClientsRegistrar.class)。
@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 {};
}
@Import(FeignClientsRegistrar.class) 導入FeignClientsRegistrar,掃描帶有註解 @FeignClient 的類注入到容器。
FeignClientsRegistrar 繼承 ImportBeanDefinitionRegistrar 完成對含有 @FeignClient 的類的註冊;注意只是Bean註冊,沒有初始化,Spring 啓動過程分註冊和初始化,初始化通過getBean()初始化;
ImportBeanDefinitionRegistrar.registerBeanDefinitions 通過 ClassPathScanningCandidateComponentProvider 掃描加了 @FeignClient 的類。
FeignClientFactoryBean 繼承了 FactoryBean , FactoryBean#getObject() 返回的就是我們的 Feign 的代理對象,最後這個代理對象被注入到了 Spring 容器中, 通過 @Autowired 可以直接注入使用。
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 註冊 EnableFeignClients 的配置類
registerDefaultConfiguration(metadata, registry);
// 註冊有 FeignClient 的接口
registerFeignClients(metadata, registry);
}
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 掃描配置註解中配置範圍內的 @FeignClient
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set<String> basePackages;
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
}
else {
final Set<String> clientClasses = new HashSet<>();
basePackages = new HashSet<>();
for (Class<?> clazz : clients) {
basePackages.add(ClassUtils.getPackageName(clazz));
clientClasses.add(clazz.getCanonicalName());
}
AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
@Override
protected boolean match(ClassMetadata metadata) {
String cleaned = metadata.getClassName().replaceAll("\\$", ".");
return clientClasses.contains(cleaned);
}
};
scanner.addIncludeFilter(
new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
}
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// 驗證被註解的類是否是接口
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);
// 注入IOC 容器
registerClientConfiguration(registry, name,
attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
// 註冊 FeignClient
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
String contextId = getContextId(attributes);
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);
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be
// null
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}