開始 初始化Feign客戶端當然是整個過程中的核心部分,畢竟初始化完畢就等著調用了,初始化時候準備的什麼,流程就走什麼。 內容 從上一篇中,我們已經知道,對於掃描到的每一個有@FeignClient,都會組裝一個FactoryBean即FeignClientFactoryBean註冊到spring容 ...
開始
初始化Feign客戶端當然是整個過程中的核心部分,畢竟初始化完畢就等著調用了,初始化時候準備的什麼,流程就走什麼。
內容
從上一篇中,我們已經知道,對於掃描到的每一個有@FeignClient,都會組裝一個FactoryBean即FeignClientFactoryBean註冊到spring容器中,如此在spring 容器初始化的時候,創建FeignClient的Bean時都會調用FeignClientFactoryBean的getObject方法。
FeignClientFactoryBean是Spring的FactoryBean,在Spring的世界里可以通過xml定義bean,也可以通過@Bean註解的方法組裝bean,但如果我們要的bean產生過程比較複雜,使用配置或單純的new不好解決,這時候使用FactoryBean就比較合適了,在Spring中想要找某個類型的bean時,如果是FactoryBean定義的,就會調用它的getObject獲取這個bean。
FeignClientFactoryBean的getObject方法:
public Object getObject() throws Exception {
FeignContext context = applicationContext.getBean(FeignContext.class);
// 構建Feign.Builder
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
String url;
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
return loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not lod balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient)client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}
構建feign.builder時會向FeignContext獲取配置的Encoder,Decoder等各種信息。FeignContext在上篇中已經提到會為每個Feign客戶端分配了一個容器,它們的父容器就是spring容器,凡是在子容器中找不到的對象,再從父容器中找。
我們可以在Feign.Builder中看全部的可配置的屬性,會發現有些信息在feignclient註解上有可以直接通過註解屬性欄位進行設置,比如ecode404,而有些屬性是只能通過註解屬性configuration配置configuration類來註入配置信息,比如:Retryer。另外除了通過在註解屬性上進行配置信息外,也可以通過FeignClientProperties來配置這些信息。
在configureFeign方法中看到可以通通過defaultToProperties屬性來控制兩者的優先順序,預設為true,比如defaultToProperties設置為false時,則會先向Feign.Builder放配置文件配置的信息,然後再放註解上配置的,後放的當然可以覆蓋先放的,所以註解配置的優先順序就算高的(除了RequestInterceptor,這個是沒有什麼優先順序的,是add上去的)。
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);
// @formatter:off
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));
// @formatter:on
configureFeign(context, builder);
return builder;
}
protected void configureFeign(FeignContext context, Feign.Builder builder) {
FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);
if (properties != null) {
if (properties.isDefaultToProperties()) {
configureUsingConfiguration(context, builder);
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(this.name), builder);
} else {
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(this.name), builder);
configureUsingConfiguration(context, builder);
}
} else {
configureUsingConfiguration(context, builder);
}
}
protected void configureUsingConfiguration(FeignContext context, Feign.Builder builder) {
Logger.Level level = getOptional(context, Logger.Level.class);
if (level != null) {
builder.logLevel(level);
}
Retryer retryer = getOptional(context, Retryer.class);
if (retryer != null) {
builder.retryer(retryer);
}
ErrorDecoder errorDecoder = getOptional(context, ErrorDecoder.class);
if (errorDecoder != null) {
builder.errorDecoder(errorDecoder);
}
Request.Options options = getOptional(context, Request.Options.class);
if (options != null) {
builder.options(options);
}
Map<String, RequestInterceptor> requestInterceptors = context.getInstances(
this.name, RequestInterceptor.class);
if (requestInterceptors != null) {
builder.requestInterceptors(requestInterceptors.values());
}
if (decode404) {
builder.decode404();
}
}
無論是通過配置文件還是註解屬性,能夠控制的都是一個feignclient整體的配置。而我們在寫feign介面的方法是,還需要定義這個介面方法的http描述信息,比如請求路徑,請求方式,參數定義等等。也就是說,對於一個單獨的請求來說,完整配置的粒度要到feign介面里的方法級別。
在getObject方法的最後會調用Targeter.target方法來組裝對象,Targeter是可以被擴展的,先不展開了,在預設的實現中會調用前面組裝好的Feign.Builder的target方法:
class DefaultTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target) {
return feign.target(target);
}
}
Feign.Builder的target方法會觸發建造者的構建操作:
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
public Feign build() {
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
}
可以想象,我們只是定義了介面,通過介面的方法我們需要達成一個請求應用的操作,肯定是需要產生一個類來實現這些介面的,這裡使用動態代理非常合適,那麼事情就變得簡單了,通過jdk自帶的動態代理方式為介面產生一個代理實現類。這個實現思路可以借鑒到其他的場景,比如比較熟悉的mybatis定義的mapper介面,也是不需要實現的,實現的方式和這裡是一模一樣。
這個實現從ReflectiveFeign的newInstance(target)方法開始:
public <T> T newInstance(Target<T> target) {
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 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;
}
從實現的代碼中可以看到熟悉的Proxy.newProxyInstance方法產生代理類。而這裡需要對每個定義的介面方法進行特定的處理實現,所以這裡會出現一個MethodHandler的概念,就是對應方法級別的InvocationHandler。
for迴圈是在過濾不必要的方法,有意思的一個地方:Util.isDefault(method)這個方法展開看一下:
/**
* Identifies a method as a default instance method.
*/
public static boolean isDefault(Method method) {
// Default methods are public non-abstract, non-synthetic, and non-static instance methods
// declared in an interface.
// method.isDefault() is not sufficient for our usage as it does not check
// for synthetic methods. As a result, it picks up overridden methods as well as actual default methods.
final int SYNTHETIC = 0x00001000;
return ((method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC | SYNTHETIC)) ==
Modifier.PUBLIC) && method.getDeclaringClass().isInterface();
}
註釋說,沒有使用Method.isDefault()是因為嫌棄它不夠全面的識別,說應該過濾掉合成(synthetic)方法,synthetic methods是編譯時自動加入的方法。
另外,Map<String, MethodHandler>的key是用Feign.configKey(target.type(), method)生成的,我覺得是可以通用:
public static String configKey(Class targetType, Method method) {
StringBuilder builder = new StringBuilder();
builder.append(targetType.getSimpleName());
builder.append('#').append(method.getName()).append('(');
for (Type param : method.getGenericParameterTypes()) {
param = Types.resolve(targetType, targetType, param);
builder.append(Types.getRawType(param).getSimpleName()).append(',');
}
if (method.getParameterTypes().length > 0) {
builder.deleteCharAt(builder.length() - 1);
}
return builder.append(')').toString();
}
targetToHandlersByName.apply(target);會解析介面方法上的註解,從而解析出方法粒度的特定的配置信息,然後生產一個SynchronousMethodHandler
然後需要維護一個<method,MethodHandler>的map,放入InvocationHandler的實現FeignInvocationHandler中。
在FeignInvocationHandler中的的invoke方法實現:
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();
}
return dispatch.get(method).invoke(args);
}
當代理類接到執行請求時, 通過一個map分發給對應的MethodHandler執行,如此就實現了針對每個方法的個性化代理實現。
所以,結構就是一個InvocationHandler對應多個MethodHandler:
MethodHandler的實現這裡是使用SynchronousMethodHandler,它實現的invoke方法如下:
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template);
} catch (RetryableException e) {
retryer.continueOrPropagate(e);
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
到這裡就會創建http請求模版,這部分後續再深入。
結束
可以看到產生的FeignClient的代理對象,代理了介面方法,實際會生成一個http請求模版,進行請求操作。
回到前面觸發的地方是spring調用FeignClientFactoryBean的getObject方法,所以產生的這個FeignClient的代理對象會在spring容器中,我們直接可以從spring容器中拿來使用。