你必须懂也可以懂的微服务系列三:服务调用

1.系列文章

2.前言

在了解服务注册反注册后,就该到服务调用环节了。

进行服务调用之前,需要组装请求头、请求体,构建客户端对象,通过服务提供方url地址调用远程服务。此方式虽然可以实现远程调用,但是需要使用者了解底层调用,并且还会给开发人员带来重复编码的问题。

为此,今天就来了解下SpringCloudOpenFeign是如何实现远程调用的

3.服务调用

3.1 开启Feign远程调用

当我们需要使用feign进行远程调用时,只需要在入口处加上@EnableFeignClients注解即可

3.2 扫描FeignClient注解标注的接口

@EnableFeignClients注解引入FeignClientsRegistrar配置类,该配置类会执行扫描动作,扫描项目中被@FeignClient注解标注的接口

ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
    candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
复制代码

关键代码:scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));

3.3 创建并注册FeignClientFactoryBean

遍历3.2中扫描到的组件,每个组件创建一个FeignClientFactoryBean并注入到IOC容器中,FeignClientFactoryBean的类型就是使用@FeignClient标注的接口

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
			Map<String, Object> attributes) {
    String className = annotationMetadata.getClassName();
    Class clazz = ClassUtils.resolveClassName(className, null);
    ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
        ? (ConfigurableBeanFactory) registry : null;
    String contextId = getContextId(beanFactory, attributes);
    String name = getName(attributes);
    // 1. 创建FeignClientFactoryBean
    FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
    // 2. 设置属性
    factoryBean.setBeanFactory(beanFactory);
    factoryBean.setName(name);
    factoryBean.setContextId(contextId);
    factoryBean.setType(clazz);
    BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
        factoryBean.setUrl(getUrl(beanFactory, attributes));
        factoryBean.setPath(getPath(beanFactory, attributes));
        factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
        Object fallback = attributes.get("fallback");
        if (fallback != null) {
            factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
                                    : ClassUtils.resolveClassName(fallback.toString(), null));
        }
        Object fallbackFactory = attributes.get("fallbackFactory");
        if (fallbackFactory != null) {
            factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
                                           : ClassUtils.resolveClassName(fallbackFactory.toString(), null));
        }
        return factoryBean.getObject();
    });
    definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    definition.setLazyInit(true);
    validate(attributes);

    AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
    beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
    beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);

    // has a default, won't be null
    boolean primary = (Boolean) attributes.get("primary");

    beanDefinition.setPrimary(primary);

    String[] qualifiers = getQualifiers(attributes);
    if (ObjectUtils.isEmpty(qualifiers)) {
        qualifiers = new String[] { contextId + "FeignClient" };
    }

    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
    // 3.注入到IOC容器中
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
复制代码

3.4 注入FeignClientFactoryBean

当我们调用远程服务接口时,往往需要通过@Autowired注解的方式注入服务,然后调用对应的方法

@RestController
public class OpenfeignConsumerController {

    @Autowired
    private IndexClient indexClient;

    @GetMapping("/index/{name}")
    public String index(@PathVariable(name = "name") String name) {
        return indexClient.index(name);
    }
}

@FeignClient(value = "openfeign-provider-service")
public interface IndexClient {

    @GetMapping("/index/{name}")
    String index(@PathVariable(name = "name") String name);
}
复制代码

IndexClient@FeignClient注解标注,根据3.3章节可以了解到其实际上是一个FeignClientFactoryBean

FeignClientFactoryBean实现了FactoryBean接口,因此当使用@Autowired注解进行注入的时候,注入的是FeignClientFactoryBeangetObject()方法返回的对象

3.5 FeignContext

从命名可以得知FeignContextfeign的上下文,其存在着一个Map类型的context属性

private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
复制代码

也就是说,每个@FeignClient对应一个AnnotationConfigApplicationContext上下文,本身应用也存在这一个AnnotationConfigApplicationContext

因此就形成了父子上下文

3.6 构建Feign Builder

Builder builder = this.feign(context);
复制代码
protected Builder feign(FeignContext context) {
    FeignLoggerFactory loggerFactory = (FeignLoggerFactory)this.get(context, FeignLoggerFactory.class);
    Logger logger = loggerFactory.create(this.type);
    Builder builder = ((Builder)this.get(context, Builder.class)).logger(logger).encoder((Encoder)this.get(context, Encoder.class)).decoder((Decoder)this.get(context, Decoder.class)).contract((Contract)this.get(context, Contract.class));
    this.configureFeign(context, builder);
    this.applyBuildCustomizers(context, builder);
    return builder;
}
复制代码

构建Feign Builder就是从容器中获取相关组件进行设置,然后在对Feign Builder进行个性化配置

3.7 从容器获取组件

3.6章节中this.get(context, xxxx.class))这个方法频繁出现,需要了解一下该方法具体实现,此处以this.get(context, FeignLoggerFactory.class)为例

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);
    } else {
        return instance;
    }
}
复制代码

3.5章节中介绍过 FeignContext,其维护了一个子容器集合,因此首先会先从子容器集合中获取指定名称的子容器

既然FeignContext维护了子容器集合,那么就必须了解子容器是如何创建的

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();
    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);
        // jdk11 issue
        // https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
        context.setClassLoader(this.parent.getClassLoader());
    }
    context.setDisplayName(generateDisplayName(name));
    context.refresh();
    return context;
}
复制代码

每次先从集合中取,如果集合中没有对应的子容器则进行创建,然后让容器中注册了PropertyPlaceholderAutoConfigurationthis.defaultConfigType

通过FeignContext的构造函数,我们可以了解到this.defaultConfigType就是FeignClientsConfiguration

打开FeignClientsConfiguration可以看到里面声明了EncoderDecoderContractBean

3.8 小结

到这里脑海中应该有个大概的总结:

  • @FeignClient对应FeignClientFactoryBean
  • 注入@FeignClient标注的接口,实际上注入的是FeignClientFactoryBeangetObject()返回的对象
  • FeignContext做为feign的上下文,为每个@FeignClient创建一个子容器,子容器中声明所需要的Bean
  • 之所以为每个@FeignClient声明一个子容器,是会了让@FeignClient@FeignClient的配置进行隔离

3.9 创建Feign实例

之前通过Feign Builder链式方式设置了相关属性,现在就可以通过Feign Builder来创建Feign实例

public Feign build() {
    Client client = Capability.enrich(this.client, capabilities);
    Retryer retryer = Capability.enrich(this.retryer, capabilities);
    List<RequestInterceptor> requestInterceptors = this.requestInterceptors.stream()
        .map(ri -> Capability.enrich(ri, capabilities))
        .collect(Collectors.toList());
    Logger logger = Capability.enrich(this.logger, capabilities);
    Contract contract = Capability.enrich(this.contract, capabilities);
    Options options = Capability.enrich(this.options, capabilities);
    Encoder encoder = Capability.enrich(this.encoder, capabilities);
    Decoder decoder = Capability.enrich(this.decoder, capabilities);
    // 1.InvocationHandlerFactory用于创建InvocationHandler
    InvocationHandlerFactory invocationHandlerFactory =
        Capability.enrich(this.invocationHandlerFactory, capabilities);
    QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities);
	// 2.SynchronousMethodHandler用于创建SynchronousMethodHandler
    SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
        new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
                                             logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
    ParseHandlersByName handlersByName =
        new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
                                errorDecoder, synchronousMethodHandlerFactory);
    return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
复制代码

3.10 创建代理

@Override
public <T> T newInstance(Target<T> target) {
    // 1.遍历@FeignClient标注接口中的方法,通过SynchronousMethodHandler.Factory创建 MethodHandler
    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 {
            // 2. 构建Method与MethodHandler的映射关系,用于方法路由
            methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
        }
    }
    // 3. 通过InvocationHandlerFactory创建InvocationHandler
    InvocationHandler handler = factory.create(target, methodToHandler);
    // 4. 生成代理对象
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
                                         new Class<?>[] {target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
        defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
}
复制代码

到此我们可以得知

@Autowired
private IndexClient indexClient;
复制代码

注入的是一个代理对象,当调用IndexClient方法的时候,会回调ReflectiveFeign.FeignInvocationHandlerinvoke方法

3.11 方法调用

@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);
}
复制代码

如上根据方法路由到对应的SynchronousMethodHandler,然后调用其invoke()

@Override
public Object invoke(Object[] argv) throws Throwable {
    // 1.构建请求模板
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
        try {
            // 2.通过http方式发起远程调用并返回结果
            return executeAndDecode(template, options);
        } catch (RetryableException e) {
            try {
                retryer.continueOrPropagate(e);
            } catch (RetryableException th) {
                Throwable cause = th.getCause();
                if (propagationPolicy == UNWRAP && cause != null) {
                    throw cause;
                } else {
                    throw th;
                }
            }
            if (logLevel != Logger.Level.NONE) {
                logger.logRetry(metadata.configKey(), logLevel);
            }
            continue;
        }
    }
}
复制代码

到此就实现了远程服务调用,可以看到使用Openfeign进行远程调用十分简单,只需搭配相关注解就可以像调用本地方法一样调用远程服务

4.结语

通过本文Openfeign讲解后,你也可以动手尝试使用HttpClient配合动态代理实现一个RPC框架,满足自己的成就感