『OpenFeign』阅读 Feign 源码,学到了什么?

122 阅读3分钟

前言

  前几天,我们尝试阅读了 Feign 的源码,那么在本次阅读中。我们学习到了什么呢?

  本篇就来总一下。

Feign 核心 - 代理模式的使用

  Feign 的总体思路,还是通过动态代理的方式来帮我们做增强。

  我们定义一个下面这样的接口:

@FeignClient("stores")
public interface StoreClient {
    @RequestMapping(method = RequestMethod.GET, value = "/stores")
    Page<Store> getStores(Pageable pageable);
}

  Feign 总过分析类上注解 @FeignClient("stores")、方法上注解@RequestMapping(method = RequestMethod.GET, value = "/stores")、方法入参、方法返回值,来为我们构造一个 HTTP 请求。

  整体流程如下:

image.png

Spring Boot Starter 中结合代理模式的正确姿势

  根据阅读 Feign 的源码,我们其实可以总结一套在 Spring Boot Starter 中使用代理模式的正确姿势。

  第一步:创建启动注解/功能注解

  启动注解就是 @EnableXxxClients,其中有一些属性可以定义需要扫描哪些类,以及一些公共配置属性。

  功能注解就是 @XxxClient,其中可以定义一些实现功能需要的业务属性,以及一些针对性的配置类。

  第二步:创建自己的 FactoryBean。

  这里主要实现 getObject() 方法,在此方法中,根据传过来的 type 类型,创建对应的代理对象。

  可以根据需要,决定采用 Cglib 还是 JDK 的动态代理。

public class XxxFactoryBean implements FactoryBean<Object> {
    private Class<?> type;
    private String name;
    Object getObject() throws Exception {
        // ....
    };
}

  第三步:开发 ImportBeanDefinitionRegistrar,也就是 BD注册器。

  用于把扫描出来的类,包装为 XxxFactoryBean ,随后用 XxxFactoryBean 代替扫描出的类,注册到 Spring Boot 的 BD 注册器中。

class XxxxClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
        XxxFactoryBean factoryBean = new XxxFactoryBean();
        // ... 填充一些需要的业务属性
        factoryBean.setName(name);
        factoryBean.setType(clazz);
        BeanDefinitionBuilder definition = BeanDefinitionBuilder
                .genericBeanDefinition(clazz, () -> {
                    // ... 填充一些需要的业务属性
                    return factoryBean.getObject();
                });
        // 注册
        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,qualifiers);
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }
}

  在 启动注解 @EnableXxxClients,上导入XxxxClientsRegistrar

@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
    // ... 一些自定义的属性
}

  第四步:开发自动配置类

  开发自动配置类,加载需要的配置项。

- resource
    - META-INF
        - spring.properties
org.springframework.boot.autoconfigure.EnableAutoConfiguration= com.xxx.xxx.xxx.XxxConguration.class

为第三方预留扩展

  这个需要代码抽象灵活一些,用更通俗地话说能用父类的地方不用子类。

  我们随便数数 Feign 提供的可配置的增强功能,HTTP 请求客户端、熔断、负载均衡、请求前置处理器、编解码器、错误解码器等等。

  让我们来看一段代码,这是获取代理类的一段代码,这段代码就很能体现可扩展性。

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
        HardCodedTarget<T> target) {
    // Http 请求,各 HTTP 请求客户端 可以实现 Client 从而来接入 Feign 。
    Client client = getOptional(context, Client.class);
    if (client != null) {
        builder.client(client);
        // 获取代理对象功能类,对 feign 做增强的,可以实现自己 Targeter 。
        // 例如 HystrixTargeter ,就是 Hystrix 熔断器接入 feign 的实现。
        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 or spring-cloud-starter-loadbalancer?")
}

  其实这里还体现了一点,即依赖倒置。像这里的 Client、Targeter 都是从 FeginContext 容器中获取,而不是直接创建的。

小结

  以上就是在粗略查看 Feign 源码时,获得的一些启发。

  后面,我们可以根据前面《使用与配置篇》、《原理篇》、《收获篇》,来基于 Feign 封装一些适合业务项目的小工具。例如:服务之间的数据透传,Feign 的异常封装,以及模仿 Feign 开发一个简版的 Http 调用工具。