Feign源码解析之注入IOC容器
上一篇中我们已经提到,对于被@FeignClients注解的接口,我们会根据其属性在IOC容器里注入一个FeignClientFactoryBean,而FeignClientFactoryBean实现了FactoryBean接口,因此实际上我们对该bean进行初始化后得到的是其getObject的返回值。这也是我们能够通过类似于调用服务的方法实现http请求发送的关键所在。
在了解getObject之前,我们先看一下FeignAutoConfiguration类,可以看出这是一个自动配置类。关于自动配置的内容可以通过EnableAutoConfiguration源码解析参考了解。
在FeignAutoConfiguration里通过@bean往IOC容器里注入了不少的bean,我们先了解一下FeignContext类,这是实现feign的一个相当关键的类。
@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
public class FeignAutoConfiguration {
@Autowired(required = false)
private List configurations = new ArrayList<>();
//省略其它方法
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
}
进入feignContext类,该类是泛型类NamedContextFactory的子类,除了构造方法外没有其它的额外属性和方法。
public class FeignContext extends NamedContextFactory {
public FeignContext() {
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
}
接着点击super方法进入NamedContextFactory类,构造方法中初始化了defaultConfigType 、propertySourceName 和 propertyName 的属性值。
public NamedContextFactory(Class> defaultConfigType, String propertySourceName,
String propertyName) {
this.defaultConfigType = defaultConfigType;
this.propertySourceName = propertySourceName;
this.propertyName = propertyName;
}
接着看setConfigurations方法,可以看出其将IOC容器中的所有FeignClientSpecification类以其name属性为key值组成了Map结构,并作为configurations 属性。
private Map configurations = new ConcurrentHashMap<>();
public void setConfigurations(List configurations) {
for (C client : configurations) {
this.configurations.put(client.getName(), client);
}
}
除此之外,我们还应该注意到NamedContextFactory类实现了ApplicationContextAware接口,并将当前的spring上下文applicationContext设置为parent属性。
另外,NamedContextFactory类还有一个极其重要的属性contexts,feign中的每一个client对应AnnotationConfigApplicationContext,contexts的key值就是client的name值,这一点和configurations的key值吻合。
private Map contexts = new ConcurrentHashMap<>();
getContext作为根据client的name获取applicationContext值的方法,和其它从缓存里获取值的方法没有什么区别。先判断缓存是否存在,如果不存在将生成对应的context并放入缓存,然后从缓存里进行获取。
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);
}
createContext是根基client的name生成context的方法,其将configurations中对应的configuration和默认的configuratin注入,然后注入PropertyPlaceholderAutoConfiguration类和FeignClientsConfiguration类,将name作为"feign.client.name"属性值放入environment。最后将parent属性(即当前的applicationContext)作为其父applicationContext,以便能够直接从生成的applicationContext中获取到当前applicationContext的属性。
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
if (this.configurations.containsKey(name)) {
for (Class> configuration : this.configurations.get(name)
.getConfiguration()) {
context.register(configuration);
}
}
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
context.setParent(this.parent);
}
context.setDisplayName(generateDisplayName(name));
context.refresh();
return context;
}
下面,我们回到FeignClientFactoryBean的 getObject 方法分为两种情况:
FeignClient配置了url属性,不用考虑负载均衡,而且如果client是使用ribbon封装的,需要进行解封装。
FeignClient没有配置url属性,根据name属性封装url,并且要求是用来负载均衡。
@Override
public Object getObject() throws Exception {
FeignContext context = applicationContext.getBean(FeignContext.class);
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));
}
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?");
}
在上面的方法里,我们看到了不少使用get和getOption方法获取值的地方,其实这两个方法都是从name对应的application中调用getBean方法获取值,只是get方法比getOption多了一个判空条件,当返回null值时,get方法会抛出异常。
protected T get(FeignContext context, Class type) {
T instance = context.getInstance(this.name, type);
if (instance == null) {
throw new IllegalStateException("No bean found of type " + type + " for "
+ this.name);
}
return instance;
}
protected T getOptional(FeignContext context, Class type) {
return context.getInstance(this.name, type);
}
这是FeignContext类的getInstance方法,可以看到这里用来了我们前面说的getContext方法从contexts中获取对应的applicationContext。
public T getInstance(String name, Class type) {
AnnotationConfigApplicationContext context = getContext(name);
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
type).length > 0) {
return context.getBean(type);
}
return null;
}
另外,我们需要关注Feign.Builder builder = feign(context);这一句代码,可以看到,主要目的是根据applicationContext和配置文件设置各种feign相关的属性。
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;
}
从configureFeign方法可以看出,feign.client.defaultConfig决定了applicationContext和配置文件中feign相关配置的优先级,当其为true时配置文件中的配置可以对根据applicationContext获取到的配置进行覆盖,false时则相反。
对于同样是配置文件中的配置,指定feign的name的配置又可以覆盖default下的配置。
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);
}
}
从getObject方法可以看到,虽然分成了有没有配置url属性两种情况,但是从代码角度最终都调用了targeter.target来获得返回值。
targeter是通过get方法得到的,从前面已经知道,其实就是从IOC容器里进行获取,因此其默认对应的是FeignAutoConfiguration的嵌套类DefaultFeignTargeterConfiguration 中注入的DefaultTargeter。
@Configuration
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new DefaultTargeter();
}
}
进入DefaultTargeter类,查看target方法。
class DefaultTargeter implements Targeter {
@Override
public T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget target) {
return feign.target(target);
}
}
继续跟踪进入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);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
}
继续查看ReflectiveFeign的newInstance方法,如果对代理有所了解的话看到这儿应该能够感到熟悉,的确FeignClient使用了jdk动态代理技术生成了代理类。
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;
}
通过InvocationHandlerFactory接口的create方法生成了InvocationHandler类,而这儿的factory熟悉是从Feign传入的InvocationHandlerFactory.Default()类。
事实上,在Feign类里定义了许多相关类的默认值,前文我们也讲到了Feign.Builder builder = feign(context)通过applicationContext和配置文件可以对属性进行设置。
private final List requestInterceptors =
new ArrayList();
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();
private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
private Options options = new Options();
private InvocationHandlerFactory invocationHandlerFactory =
new InvocationHandlerFactory.Default();
InvocationHandlerFactory.Default()的create方法new了一个新的InvocationHandler类。
public interface InvocationHandlerFactory {
InvocationHandler create(Target target, Map dispatch);
/**
* Like {@link InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])}, except for a
* single method.
*/
interface MethodHandler {
Object invoke(Object[] argv) throws Throwable;
}
static final class Default implements InvocationHandlerFactory {
@Override
public InvocationHandler create(Target target, Map dispatch) {
return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
}
}
}
FeignInvocationHandler 就是我们处理Feign默认的InvocationHandler,通过invoke方法对处理equal、hashcode、toString以外的方法做Http请求的处理。
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);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof FeignInvocationHandler) {
FeignInvocationHandler other = (FeignInvocationHandler) obj;
return target.equals(other.target);
}
return false;
}
@Override
public int hashCode() {
return target.hashCode();
}
@Override
public String toString() {
return target.toString();
}
}