一. 前言
關(guān)于Feign的啟動原理分析,參照另一篇Spring Cloud Feign 源碼分析 - feign啟動原理
二. 源碼分析
書接上文,上篇最后提到所有帶@FeignClient注解的interface都被封裝成FeignClientFactoryBean的BeanDefinition。從名字上可以得知這個類是一個FactoryBean。關(guān)于FactoryBean的介紹參考...
因此直接找getObject()。
@Override
public Object getObject() throws Exception {
return getTarget();
}
/**
* @param <T> 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 context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
return (T) 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 load 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 (T) targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}
getTarget方法首先獲取FeignContext的對象,基于這個context對當(dāng)前feign的配置信息存放到Builder中。
FeignContext context = applicationContext.getBean(FeignContext.class);
首先實例化bean:FeignContext
FeignContext的定義在FeignAutoConfiguration
@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
public class FeignAutoConfiguration {
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public HasFeatures feignFeature() {
return HasFeatures.namedFeature("Feign", Feign.class);
}
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
第一次除了創(chuàng)建新的FeignContext對象之外,還設(shè)置了一組configurations,
這組configurations是FeignClientSpecification類型,通過autowired注入。
在掃描EnableFeignClients和各個FeignClient時,將configuration對應(yīng)的class封裝成了FeignClientSpecification的BeanDefinition,這里從容器中取出來創(chuàng)建對象注入到configurations


通過斷點可以看到這里有15個FeignClientSpecification的對象

一個是default.開頭的在啟動類里配置的configuration,剩下的都是FeignClient的configuration。
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
public FeignContext() {
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
}
FeignContext繼承了NamedContextFactory,對應(yīng)的范型就是FeignClientSpecification,看下NamedContextFactory構(gòu)造方法
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
implements DisposableBean, ApplicationContextAware {
public interface Specification {
String getName();
Class<?>[] getConfiguration();
}
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
private Map<String, C> configurations = new ConcurrentHashMap<>();
private ApplicationContext parent;
private Class<?> defaultConfigType;
private final String propertySourceName;
private final String propertyName;
public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
String propertyName) {
this.defaultConfigType = defaultConfigType;
this.propertySourceName = propertySourceName;
this.propertyName = propertyName;
}
這里設(shè)置了默認的defaultConfigType,feign里用的是FeignClientsConfiguration,定義了一系列的默認值。
//分析這一句
Feign.Builder builder = feign(context);
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 <T> T get(FeignContext context, Class<T> type) {
T instance = context.getInstance(this.contextId, type);
if (instance == null) {
throw new IllegalStateException("No bean found of type " + type + " for " + this.contextId);
}
return instance;
}
在獲取到FeignContext之后,開始封裝Feign.Builder。
首先通過context實例化FeignLoggerFactory的對象,因為context是NamedContextFactory的子類,會給每個contextId創(chuàng)建一個獨立的AnnotationConfigApplicationContext上下文,每一個k-v會存儲在FeignContext的全局context中,key就是contextId
public <T> T getInstance(String name, Class<T> type) {
AnnotationConfigApplicationContext context = getContext(name);
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
type).length > 0) {
return context.getBean(type);
}
return null;
}
protected AnnotationConfigApplicationContext getContext(String name) {
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
this.contexts.put(name, createContext(name));
}
}
}
return this.contexts.get(name);
}
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//1 給FeignClient設(shè)置獨立的configuration
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
context.register(configuration);
}
}
//2 給FeignClient設(shè)置全局defaultConfiguration
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
context.register(PropertyPlaceholderAutoConfiguration.class,
this.defaultConfigType);
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
this.propertySourceName,
Collections.<String, Object> singletonMap(this.propertyName, name)));
if (this.parent != null) {
// Uses Environment from parent as well as beans
//給子上下文設(shè)置parentContext
context.setParent(this.parent);
}
context.setDisplayName(generateDisplayName(name));
//子上下文調(diào)用refresh方法,刷新操作
context.refresh();
return context;
}
這三個方法的實現(xiàn)完全體現(xiàn)了NamedContextFactory的作用:
給每個name創(chuàng)建一個單獨的ApplicationContext子上下文對象,后續(xù)凡是這個name的ioc操作,都由獨立的ApplicationContext來完成,name之間的context相互隔離。所有的子上下文保存在了Map<String, AnnotationConfigApplicationContext> contexts中。
在創(chuàng)建Context時,補充了configuration的設(shè)置:
首先(1的位置),從全局的configurations查找是否定義了只對當(dāng)前name生效的configuration,也就是判斷在當(dāng)前name所屬的FeignClient注解上是否定義了configuration。如果定義過,將這個configuration的Class封裝成BeanDefinition注冊到本name的子上下文中。
接著(2的位置),從全局的configurations查找是否定義了全局配置,也就是@EnableFeignClients的defaultConfiguration的值,這里固定前綴是default.。
如果也存在,就也將這個defaultConfiguration的Class封裝成BeanDefinition注冊到本name的子上下文中。
第一次調(diào)用完畢get方法后,給每個FeignClient創(chuàng)建的FeignContext就完成了configuration初始化的動作,后面的所有操作,如配置encoder、decoder都是給當(dāng)前的子上下文內(nèi)注冊BeanDefinition。最后將所有配置封裝成Builder返回。
三. 請求調(diào)度
在getTarget()構(gòu)造完成builder屬性之后,開始了整個請求調(diào)度過程。
先看第一段:
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
}
如果沒有url屬性,就用name來處理,把http:// + name + path 拼裝成url,執(zhí)行l(wèi)oadBalance()
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
首先實例化Client的bean對象,默認返回LoadBalancerFeignClient的實例。
@Configuration
class DefaultFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null),
cachingFactory, clientFactory);
}
}
public class LoadBalancerFeignClient implements Client {
//...
private final Client delegate;
public LoadBalancerFeignClient(Client delegate, CachingSpringLoadBalancerFactory lbClientFactory, SpringClientFactory clientFactory) {
this.delegate = delegate;
this.lbClientFactory = lbClientFactory;
this.clientFactory = clientFactory;
}
從LoadBalancerFeignClient的構(gòu)造方法可以看到,這里使用了delegate的設(shè)計模式來代理Client.Default,擴展execute的實現(xiàn)。
然后則繼續(xù)實例化Targeter的bean。默認有兩種實現(xiàn)類。
@Configuration
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
protected static class HystrixFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new HystrixTargeter();
}
}
@Configuration
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new DefaultTargeter();
}
}
我這里返回HystrixTargeter。調(diào)用target方法。
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target) {
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
return feign.target(target);
}
feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
SetterFactory setterFactory = getOptional(factory.getName(), context,
SetterFactory.class);
if (setterFactory != null) {
builder.setterFactory(setterFactory);
}
Class<?> fallback = factory.getFallback();
if (fallback != void.class) {
return targetWithFallback(factory.getName(), context, target, builder, fallback);
}
Class<?> fallbackFactory = factory.getFallbackFactory();
if (fallbackFactory != void.class) {
return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
}
return feign.target(target);
}
這里重點看下feign.target(target)的實現(xiàn)。
public abstract class Feign {
//...
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, closeAfterDecode, propagationPolicy);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
}
}
可以看到,通過build()構(gòu)造了一個ReflectiveFeign的對象,將一系列feign的參數(shù)封裝成了SynchronousMethodHandler和ParseHandlersByName。封裝的這兩個對象都是為了給后面newInstance用的。
newInstance返回了擴展后的Targeter的代理類。

下面介紹下newInstance的詳細過程。
@Override
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;
}
public Map<String, MethodHandler> apply(Target key) {
List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
for (MethodMetadata md : metadata) {
BuildTemplateByResolvingArgs buildTemplate;
if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
} else if (md.bodyIndex() != null) {
buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
} else {
buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder);
}
result.put(md.configKey(),
factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
}
return result;
}
}
apply方法就是給feignClient的每個方法都封裝了一個SynchronousMethodHandler,
factory.create(...)就是為了根據(jù)當(dāng)前方法的各個參數(shù)+new SynchronousMethodHandler.Factory定義的默認參數(shù)來構(gòu)造SynchronousMethodHandler
key對應(yīng)的是類名#方法名,如:MasterClientLocal#getPersons()。
for循環(huán)則是為了封裝methodToHandler,k-v分別是reflect的Method和SynchronousMethodHandler。遍歷完成后,構(gòu)建一個InvocationHandler的實現(xiàn)類:FeignInvocationHandler
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
}
}
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();
}
return dispatch.get(method).invoke(args);
}
通過傳入target和dispatch,其實本質(zhì)就是在調(diào)用SynchronousMethodHandler的invoke方法。而invoke方法則是擴展了http的調(diào)用動作,包括請求重試,decode處理,decode404判斷等。
@Override
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) {
//...
continue;
}
}
}
Object executeAndDecode(RequestTemplate template) throws Throwable {
Request request = targetRequest(template);
Response response;
long start = System.nanoTime();
try {
response = client.execute(request, options);
} catch (IOException e) {
//...
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
boolean shouldClose = true;
try {
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) {
//...
} finally {
if (shouldClose) {
ensureClosed(response.body());
}
}
}
最重要的執(zhí)行則是client.execute(...);
client有兩種實現(xiàn)類:

Default是帶url的execute的實現(xiàn),封裝了最普通的http調(diào)用。
LoadBalanceFeignClient是eureka的實現(xiàn),通過獲取server列表來實現(xiàn)loadBalance。
也就是最開始getTarget() 方法的兩段不同的實現(xiàn)過程的最本質(zhì)區(qū)別。
至此,F(xiàn)eignClientFactoryBean的源碼分析告一段落。
四. 總結(jié)
- getTarget的最終目的是給每個feignClient的方法封裝一個HardCodedTarget的代理對象。代理的目的是實現(xiàn)通用擴展(重試、decode、decode404等)和loadbalance擴展
- 區(qū)別在于feign的注解里是否有url的屬性
- 如果有則執(zhí)行的是Default的實現(xiàn)類,封裝普通的http調(diào)用,
- 如果沒有url則執(zhí)行LoadBalanceFeignClient的execute方法,包裝了一層獲取server列表來實現(xiàn)負載均衡的功能。
- 這里的擴展方式使用的是delegate的設(shè)計模式,如果想繼續(xù)擴展,依然可以沿用這種方式。
本人通過delegate方式在此基礎(chǔ)上實現(xiàn)了traceId的跨feign傳遞。將在下一篇文章中做具體說明。