Feign源码解析之生成jdk动态代理

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();
   }
 }

你可能感兴趣的:(Feign,源码)