手写RPC框架Feign

手写RPC框架Feign

阅读本文你可获得:
1、RPC原理
2、feign注入原理
3、如何手写feign框架
4、动态代码设计模式应用场景

什么是RPC

RPC:远程过程调用,也就是我们通常说“HTTP”调用,也有人说dubbo不是基于HTTP也是RPC啊,那怎么理解这件事呢?其实RPC就是指两服务之间调用不再写繁琐代码,有个东西把这件事包装起来,像写普通方法一样调用远程服务。

有人从网络分层(5层)角度出发说:feign不属于RPC,dubbo才是真正的RPC,feign底层是HTTP(应用层)或7层调用,dubbo是4层(传输层)调用,真正PRC是4层而不是7层。也有人说RPC就是HTTP,在此小编认为两者说的不准确,RPC不是区分实现过程(4层、7层)只区分调用手段(像调用普通service一样)。

从宏观角度讲只要是开发人员调用外部服务像调用方法一样的框架都叫RPC。

Feign注入原理

使用feign需要在启动类上加@EnableFeignClients注解并写上包名,我们在类上加@FeignClient注解、方法加@GetMapping等注解并配置相应属性feign架构就起作用,那么从这里思考这几个注解起了什么作用?我们倒着分析。

  1. GetMapping、PostMapping 这几个注解同Controller一样这里不再描述;
  2. FeignClient 是对我们类名、请求地址进行统一配置这里不再描述;
  3. EnableFeignClients 这个注解相当重要且它是核心注解,以上两个注解是否起作用,怎么发起HTTP调用都是该注解在工作;
EnableFeignClients

注解上有一个@Import(FeignClientsRegistrar.class)核心代码在FeignClientsRegistrar类中。
该类实现接口ImportBeanDefinitionRegistrar它是Spring扩展点之一,支持我们写的代码封装成BeanDefinition俗称bean,bean的有功能它都有例如:@Autowired注解该bean、postProcessAfterInitializationbean的后置初始化等功能。

  • registerBeanDefinitions 方法中两个方法:registerDefaultConfiguration注册默认配置和registerFeignClients注册feign的配置,我们注入feign的配置所以核心在registerFeignClients方法中。

在这里插入图片描述
scanner是项目启动扫描所有class文件,并设置过滤条件(类上有FeignClient注解),且通过metadata(启动类上EnableFeignClients注解配置包名)并获得注解所扫描对应包名。

在这里插入图片描述
拿到启动类上所有包类,过滤上一步设置好的条件(带FeignClient注解的)类,找到我们定义FeignClient接口类。

在这里插入图片描述
上图四步feign帮我们做了,其实第三步对于我们手写框架来说可以忽略,重要看第四步。

在这里插入图片描述
第一步:构建FeignClientFactoryBean并通过构造函数设置值(生成代理核心)。
第二步:获得bean别名、并设置bean是否是主要bean。
第三步:向Spring中注册bean。


致此我们写的带FeignClient注解的接口注册到spring中,可以通过@Autowired@Resource等方式注入到代码里开始使用,那么有的小伙伴就会问我的实现还没写呢?调用逻辑呢?

Feign调用入口

Java动态代理分为两种:JDK和CGLIB,它两本质区别是有没有实现接口,JDK必须是实现接口,CGLIB可以不用实现接口。feign用的是JDK动态代理所以我们写代码时FeignClient注解要放在接口,刚才注入bean过程也进行了判断,非接口不让注解会报错(第三张图有分析)。

我们最后一张图有说:构建FeignClientFactoryBean并通过构造函数设置值,这里是产生代理核心。该主要实现了FactoryBean返回值就是我们注册到spring中bean的类型,在该类中可以注入其它bean,感觉兴趣的小伙伴可以从源一直点下去,在newInstance类中会生成代理对象,如下图。

在动态代理类中编写具体实现代码。
在这里插入图片描述

以上核对源码分析完成,我们分析源码主要目的是为实现简单feign做铺垫。


手写Feign

如果我们要写一个简单版本的feign那应该怎么做呢?
1、我们需要把接口类注入到spring中。
2、在动态代理中编写实现方法。

听着很简单实际操作也很简单(>‿<)

  • 定义启动类注解MonkeyEnableHttpClient并配置扫描包同EnableFeignClients注解,我们只需要value一个注解即可。
/**
 * @Author: LailaiMonkey
 * @Description:
 * @Date:Created in 2022/09/15 4:11 下午
 * @Modified By:
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MonkeyHttpClientRegister.class)
public @interface MonkeyEnableHttpClient {
   
    String[] value();
}
  • 定义接口类注解MonkeyHttpClientFeignClient注解,需要知道bean名称(通过反射自动生成)、该类配置全局url、超时时间等信息。
/**
 * @Author: LailaiMonkey
 * @Description:
 * @Date:Created in 2022/09/15 2:48 下午
 * @Modified By:
 */
@Target({
   ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MonkeyHttpClient {
   

    /**
     * 注册bean名称,默认类名
     *
     * @return
     */
    String name() default "";

    /**
     * 调用url
     *
     * @return
     */
    String url();

    /**
     * 读超时时间
     *
     * @return
     */
    String readTimeout() default "5000";

    /**
     * 连接超时时间
     *
     * @return
     */
    String connectTimeout() default "5000";

}
  • 定义注册类MonkeyHttpClientRegister启动时扫描包并注册到Spring中。
/**
 * @Author: LailaiMonkey
 * @Description:
 * @Date:Created in 2022/09/15 4:04 下午
 * @Modified By:
 */
@Slf4j
public class MonkeyHttpClientRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware, BeanClassLoaderAware, ResourceLoaderAware {
   

    private Environment environment;

    private ClassLoader classLoader;

    private ResourceLoader resourceLoader;


    @Override
    public void setEnvironment(Environment environment) {
   
        this.environment = environment;
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
   
        this.classLoader = classLoader;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
   
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
   
        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        scanner.setResourceLoader(this.resourceLoader);
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(MonkeyHttpClient.class);
        scanner.addIncludeFilter(annotationTypeFilter);

        Map<String, Object> attributes = metadata.getAnnotationAttributes(MonkeyEnableHttpClient.class.getCanonicalName());
        if (CollectionUtils.isEmpty(attributes)) {
   
            return;
        }

        Set<String> basePackages = new HashSet<>();
        for (String pkg : (String[]) attributes.get("value")) {
   
            if (StringUtils.hasText(pkg)) {
   
                basePackages.add(pkg);
            }
        }

        for (String basePackage : basePackages) {
   
            Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
            for (BeanDefinition candidateComponent : candidateComponents) {
   
                if (candidateComponent instanceof AnnotatedBeanDefinition) {
   

                    // verify annotated class is an interface
                    AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                    AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                    Assert.isTrue(annotationMetadata.isInterface(), "@GZHttpClient can only be specified on an interface");

                    Map<String, Object> feignClientAttributeMap = annotationMetadata.getAnnotationAttributes(MonkeyHttpClient.class.getCanonicalName());
                    if (CollectionUtils.isEmpty(feignClientAttributeMap)) {
   
                        return;
                    }

                    String className = annotationMetadata.getClassName();

                    Class<?> clazz = null;
                    try {
   
                        clazz = Class.forName(className);
                    } catch (ClassNotFoundException e) {
   
                        log.error("httpClient start up fail:", e);
                    }

                    String beanName = className.substring(className.lastIndexOf(".") + 1);
                    String alias = beanName.substring(0, 1).toLowerCase().concat(beanName.substring(1)).concat("HttpClient");
                    String name = String.valueOf(feignClientAttributeMap.get("name"));
                    String url = String.valueOf(feignClientAttributeMap.get("url"));
                    if (!StringUtils.isEmpty(name)) {
   
                        alias = name;
                    }
                    String readTimeout = String.valueOf(feignClientAttributeMap.get("readTimeout"));
                    String connectTimeout = String.valueOf(feignClientAttributeMap.get("connectTimeout"));

                    BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(MonkeyHttpClientFactoryBean.class);
                    definition.addConstructorArgValue(clazz);
                    definition.addConstructorArgValue(url);
                    definition.addConstructorArgValue(readTimeout);
                    definition.addConstructorArgValue(connectTimeout);
                    definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

                    AbstractBeanDefinition handleDefinition = definition.getBeanDefinition();
                    handleDefinition.setPrimary(true);

                    // 向Spring的上下文中注册bean组件
                    BeanDefinitionHolder holder = new BeanDefinitionHolder(handleDefinition, className, new String[]{
   alias});
                    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
                }
            }
        }
    }

    protected ClassPathScanningCandidateComponentProvider getScanner() {
   
        return new ClassPathScanningCandidateComponentProvider(false, this.environment
  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
当前主流的RPC(Remote Procedure Call)框架有很多,FeignDubbo是其中两个比较知名的。下面是关于它们的简要介绍: 1. **Feign**: - Feign是Netflix开发的一款轻量级的API客户端,主要用于简化服务间的接口调用。它基于Java注解,提供了一种声明式的接口调用方式,开发者只需定义接口,Feign会自动生成实现类和底层HTTP请求。 - Feign支持各种HTTP库,如OkHttp、Apache HttpClient等,并且可以轻松地集成到Spring Cloud生态系统中,方便微服务之间的通信。 2. **Dubbo**: - Dubbo是一个开源的企业级高性能远程服务调用框架,主要适用于大规模分布式服务架构,支持高并发、高性能和容错性。Dubbo基于Java,提供了一套全面的服务治理解决方案,包括注册中心、服务发现、负载均衡、熔断、限流等。 - 它通常用于企业级的微服务架构中,尤其在大型分布式系统中的服务间通信方面非常常见。 除了这两个,还有其他一些主流RPC框架,例如: - **gRPC**:Google开源的高性能RPC框架,基于Protocol Buffers协议,提供了高性能和安全性。 - **Retrofit**:Android和Java平台的流行库,结合OkHttp实现RESTful API调用。 - **Hystrix**(已被Netflix弃用):原本是Amazon的故障隔离工具,但现在常常与Feign一起作为服务降级和熔断方案使用。 如果你对这些RPC框架感兴趣,可以关注它们各自的特性、适用场景以及与其他技术栈的集成情况。有关更多细节,你可以询问: 1. Feign和Retrofit相比,各有何优缺点? 2. Dubbo和gRPC在性能上的差异体现在哪些方面? 3. 在微服务架构中,如何选择合适的RPC框架

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值