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

/ Java / 没有评论 / 1551浏览

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

前篇

正文

上一篇中我们已经提到,对于被@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<FeignClientSpecification> configurations = new ArrayList<>();

  //省略其它方法

  @Bean
  public FeignContext feignContext() {
    FeignContext context = new FeignContext();
    context.setConfigurations(this.configurations);
    return context;
  }
}

进入feignContext类,该类是泛型类NamedContextFactory的子类,除了构造方法外没有其它的额外属性和方法。

public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
  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<String, C> configurations = new ConcurrentHashMap<>();

public void setConfigurations(List<C> 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<String, AnnotationConfigApplicationContext> 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<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
    context.setParent(this.parent);
  }
  context.setDisplayName(generateDisplayName(name));
  context.refresh();
  return context;
}

下面,我们回到FeignClientFactoryBean的 getObject 方法分为两种情况:

@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> 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?");
}

在上面的方法里,我们看到了不少使用get和getOption方法获取值的地方,其实这两个方法都是从name对应的application中调用getBean方法获取值,只是get方法比getOption多了一个判空条件,当返回null值时,get方法会抛出异常。

protected <T> T get(FeignContext context, Class<T> 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> T getOptional(FeignContext context, Class<T> type) {
  return context.getInstance(this.name, type);
}

这是FeignContext类的getInstance方法,可以看到这里用来了我们前面说的getContext方法从contexts中获取对应的applicationContext。

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

另外,我们需要关注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> T target(
    FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
    Target.HardCodedTarget<T> target) {
    return feign.target(target);
  }
}

继续跟踪进入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);
  ParseHandlersByName handlersByName =
    new ParseHandlersByName(contract, options, encoder, decoder,
                            errorDecoder, synchronousMethodHandlerFactory);
  return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
}

继续查看ReflectiveFeign的newInstance方法,如果对代理有所了解的话看到这儿应该能够感到熟悉,的确FeignClient使用了jdk动态代理技术生成了代理类。

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

通过InvocationHandlerFactory接口的create方法生成了InvocationHandler类,而这儿的factory熟悉是从Feign传入的InvocationHandlerFactory.Default()类。

事实上,在Feign类里定义了许多相关类的默认值,前文我们也讲到了Feign.Builder builder = feign(context)通过applicationContext和配置文件可以对属性进行设置。

private final List<RequestInterceptor> requestInterceptors = new ArrayList<RequestInterceptor>();
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<Method, MethodHandler> 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<Method, MethodHandler> 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<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);
  }

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