一. 前言
关于Feign的启动原理分析,参照另一篇Spring Cloud Feign 源码分析 - feign启动原理
二. 源码分析
书接上文,上篇最后提到所有带@FeignClient注解的interface都被封装成FeignClientFactoryBean的BeanDefinition。从名字上可以得知这个类是一个FactoryBean。关于FactoryBean的介绍参考...
因此直接找getObject()。
@Override
public Object getObject() throws Exception {
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 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对当前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 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;
}
第一次除了创建新的FeignContext对象之外,还设置了一组configurations,
这组configurations是FeignClientSpecification类型,通过autowired注入。
在扫描EnableFeignClients和各个FeignClient时,将configuration对应的class封装成了FeignClientSpecification的BeanDefinition,这里从容器中取出来创建对象注入到configurations
通过断点可以看到这里有15个FeignClientSpecification的对象
一个是default.开头的在启动类里配置的configuration,剩下的都是FeignClient的configuration。
public class FeignContext extends NamedContextFactory {
public FeignContext() {
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
}
FeignContext继承了NamedContextFactory,对应的范型就是FeignClientSpecification,看下NamedContextFactory构造方法
public abstract class NamedContextFactory
implements DisposableBean, ApplicationContextAware {
public interface Specification {
String getName();
Class>[] getConfiguration();
}
private Map contexts = new ConcurrentHashMap<>();
private Map 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;
}
这里设置了默认的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 get(FeignContext context, Class 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创建一个独立的AnnotationConfigApplicationContext上下文,每一个k-v会存储在FeignContext的全局context中,key就是contextId
public T getInstance(String name, Class 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设置独立的configuration
if (this.configurations.containsKey(name)) {
for (Class> configuration : this.configurations.get(name)
.getConfiguration()) {
context.register(configuration);
}
}
//2 给FeignClient设置全局defaultConfiguration
for (Map.Entry 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. singletonMap(this.propertyName, name)));
if (this.parent != null) {
// Uses Environment from parent as well as beans
//给子上下文设置parentContext
context.setParent(this.parent);
}
context.setDisplayName(generateDisplayName(name));
//子上下文调用refresh方法,刷新操作
context.refresh();
return context;
}
这三个方法的实现完全体现了NamedContextFactory的作用:
给每个name创建一个单独的ApplicationContext子上下文对象,后续凡是这个name的ioc操作,都由独立的ApplicationContext来完成,name之间的context相互隔离。所有的子上下文保存在了Map
在创建Context时,补充了configuration的设置:
首先(1的位置),从全局的configurations查找是否定义了只对当前name生效的configuration,也就是判断在当前name所属的FeignClient注解上是否定义了configuration。如果定义过,将这个configuration的Class封装成BeanDefinition注册到本name的子上下文中。
接着(2的位置),从全局的configurations查找是否定义了全局配置,也就是@EnableFeignClients的defaultConfiguration的值,这里固定前缀是default.。
如果也存在,就也将这个defaultConfiguration的Class封装成BeanDefinition注册到本name的子上下文中。
第一次调用完毕get方法后,给每个FeignClient创建的FeignContext就完成了configuration初始化的动作,后面的所有操作,如配置encoder、decoder都是给当前的子上下文内注册BeanDefinition。最后将所有配置封装成Builder返回。
三. 请求调度
在getTarget()构造完成builder属性之后,开始了整个请求调度过程。
先看第一段:
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,执行loadBalance()
protected T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget 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的构造方法可以看到,这里使用了delegate的设计模式来代理Client.Default,扩展execute的实现。
然后则继续实例化Targeter的bean。默认有两种实现类。
@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。调用target方法。
@Override
public T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget 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)的实现。
public abstract class Feign {
//...
public T target(Target 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()构造了一个ReflectiveFeign的对象,将一系列feign的参数封装成了SynchronousMethodHandler和ParseHandlersByName。封装的这两个对象都是为了给后面newInstance用的。
newInstance返回了扩展后的Targeter的代理类。
下面介绍下newInstance的详细过程。
@Override
public T newInstance(Target target) {
Map nameToHandler = targetToHandlersByName.apply(target);
Map methodToHandler = new LinkedHashMap();
List defaultMethodHandlers = new LinkedList();
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 apply(Target key) {
List metadata = contract.parseAndValidatateMetadata(key.type());
Map result = new LinkedHashMap();
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(...)就是为了根据当前方法的各个参数+new SynchronousMethodHandler.Factory定义的默认参数来构造SynchronousMethodHandler
key对应的是类名#方法名,如:MasterClientLocal#getPersons()。
for循环则是为了封装methodToHandler,k-v分别是reflect的Method和SynchronousMethodHandler。遍历完成后,构建一个InvocationHandler的实现类:FeignInvocationHandler
@Override
public InvocationHandler create(Target target, Map dispatch) {
return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
}
}
static class FeignInvocationHandler implements InvocationHandler {
private final Target target;
private final Map dispatch;
FeignInvocationHandler(Target target, Map 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,其实本质就是在调用SynchronousMethodHandler的invoke方法。而invoke方法则是扩展了http的调用动作,包括请求重试,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());
}
}
}
最重要的执行则是client.execute(...);
client有两种实现类:
Default是带url的execute的实现,封装了最普通的http调用。
LoadBalanceFeignClient是eureka的实现,通过获取server列表来实现loadBalance。
也就是最开始getTarget() 方法的两段不同的实现过程的最本质区别。
至此,FeignClientFactoryBean的源码分析告一段落。
四. 总结
- getTarget的最终目的是给每个feignClient的方法封装一个HardCodedTarget的代理对象。代理的目的是实现通用扩展(重试、decode、decode404等)和loadbalance扩展
- 区别在于feign的注解里是否有url的属性
- 如果有则执行的是Default的实现类,封装普通的http调用,
- 如果没有url则执行LoadBalanceFeignClient的execute方法,包装了一层获取server列表来实现负载均衡的功能。
- 这里的扩展方式使用的是delegate的设计模式,如果想继续扩展,依然可以沿用这种方式。
本人通过delegate方式在此基础上实现了traceId的跨feign传递。将在下一篇文章中做具体说明。