基于HTTP协议实现简单RPC框架的方法详解

 更新时间:2023年06月09日 14:12:54   作者:耶耶耶耶yeyeye  
RPC全名(Remote Procedure Call),翻译过来就是远程过程调用,本文将为大家介绍如何基于HTTP协议实现简单RPC框架,感兴趣的小伙伴可以了解一下

什么是RPC

RPC全名(Remote Procedure Call),翻译过来就是远程过程调用,这里特别说明一点,RPC是一种远程调用方式,而不是一种协议。那么,我们其实很容易联想到与之对应的应该是本地过程调用(Local Procedure Call),而不是什么什么http协议、tcp协议.....

本地调用与远程调用

我们最常见的本地调用是什么样的?

public interface UserService {
    String doSomething();
}
@Service
public class UserServiceImpl implements UserService {
    @Override
    public String doSomething() {
        System.out.println("doSomething......");
    }
}
@Controller
public class UserController {
    @Autowired
    private UserService userService;
    public void test(){
        String response = userService.doSomething();
    }
}

我们以最常见的思维来模拟,这就是一种对UserService最简单的本地调用,service实现类以及对应暴露出的接口都在本地机器上,不需要网络之间的通信就能调用服务执行。

那么,怎么样又算对UserService远程调用呢?我们假设有 A, B两台机器,A为客户端,B为服务端。此时,假设我们对于UserService来说,他的impl实现类在服务端B上,客户端只知道对应的暴露接口,也就是说,对于客户端A,没有办法直接使用spring帮我们自动注入对应service使用。

此时环境下 客户端A的代码示例:

public interface UserService { //假设这是服务端暴露出来的接口
    String doSomething();
}
@Controller
public class UserController {
    @Autowired
    private UserService userService;//对于客户端来说,此时没有实现类,无法直接使用
    public void test(){
        String response = userService.doSomething();
    }
}

RPC框架就可以很好地解决这种场景下的问题,下面简单提供一个实现思路。

实现思路

我们同样以最直接最暴力的想法去思考怎么解决这个问题:只要我客户端调用 UserService 的 doSomething() 后,客户端再发送个请求并携带相关参数(如果有的话)给服务端,服务端处理后再响应给客户端即可。那怎么实现这个过程呢?由于服务端已经对我们暴露出了接口,那我们实际上可以用代理类的方式去实现,把请求响应的过程放在代理类中实现即可,只要生成了代理类,我们再将这个代理类交给spring容器管理,让他正常注入,我们就可以实现在客户端没有impl类的情况下也能正常使用service方法。

我们模仿Mybatis的@MapperScan注解,自己弄一个@RPC注解,只要将这个注解加在配置类上,即可将对应包下的接口生成RPC代理类

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({ServiceBeanDefinitionRegistry.class}) //注意看这个地方比较重要
public @interface RPC {
    String value() default ""; //扫描的路径
}
//简单用法
@SpringBootApplication
@RPC("com.yeyeye.client.service") //扫描对应包下的所有接口,service包下就一个上面示例中的 UserService 接口
public class RpcClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(RpcClientApplication.class, args);
    }
}

@Import 注解就不在这注释了,大概就是spring在启动的时候会导入这个注解里面写的类,这里详细介绍一下 ServiceBeanDefinitionRegistry

public class ServiceBeanDefinitionRegistry implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //获取RPC注解里的路径
        Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(RPC.class.getName());
        if (MapUtil.isEmpty(attributes)) {
            return;
        }
        String basePackages = (String) attributes.get("value");
        if (basePackages == null) {
            return;
        }
        //扫描路径
        RpcScanner rpcScanner = new RpcScanner(registry);
        rpcScanner.addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
        rpcScanner.scan(basePackages);
    }
}

这个类主要就做了两件事,很好理解,就是:

  • 获取@RPC注解中的路径
  • 让spring帮我们扫描这个路径,将扫描到的符合需求的bean注册到registry中。

RpcScanner 这个类是利用Spring的扫描机制写的,扫描机制具体原理不是重点,这里也不赘述。

public class RpcScanner extends ClassPathBeanDefinitionScanner {
    public RpcScanner(BeanDefinitionRegistry registry) {
        super(registry);
    }
    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        //是不是一个接口,是的话就添加为候选组件
        return beanDefinition.getMetadata().isInterface();
    }
    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        //这里就是具体利用spring帮我们扫描
        Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
        //对扫描出来的结果进行处理
        for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
            //这里获取的beanDefinition都是接口,并没有实现类
            BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();
            //注册一个我们自己的beanDefinition,这个beanDefinition是一个bean工厂,工厂用于生成接口的代理类
            beanDefinition.setBeanClassName(ServiceBeanFactory.class.getName());
            //这里就是工厂bean构造方法需要的参数
            beanDefinition.getConstructorArgumentValues()
            .addGenericArgumentValue(beanDefinition.getBeanClassName());
        }
        return beanDefinitionHolders;
    }
}

这个类比较重要,主要就干了几件事:

  • 把对应包下的接口全部扫描出来,包装成beanDefinition。
  • 由于此时的beanDefinition里面的类是一个接口,spring没办法帮我们生成,所以我们需要用一个beanFactory代替beanDefinition里的类,也就是用bean工厂帮我们生成一个代理类给spring管理
  • 添加实例化工厂需要的参数

工厂bean类,用jdk代理机制简单实现

public class ServiceBeanFactory<T> implements FactoryBean<T> {
    private Class<T> interFaceClazz;
    public ServiceBeanFactory(Class<T> interFaceClazz) {
        this.interFaceClazz = interFaceClazz;
    }
    @Override
    public T getObject() throws Exception {
        return (T) Proxy.newProxyInstance(
                interFaceClazz.getClassLoader(),
                new Class[]{interFaceClazz},
                new ServiceInvocationHandler());
    }
    @Override
    public Class<?> getObjectType() {
        return interFaceClazz;
    }
}
public class ServiceInvocationHandler implements InvocationHandler {
    /**
     * 这里就直接进行远程RPC调用了
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        //指定URL
        String url = "http://localhost:8888/" + method.getName();
        //发送get请求并接收响应数据
        return HttpUtil.createGet(url).execute().body();
    }
}

因此,spring注入的时候实际上注入的是这个对接口的代理类,我们在执行doSomething()方法时,实际上会通过这个代理类向服务端发一个HTTP请求。

服务端

对于服务端来说,我只需要正常写一个controller处理一下来自客户端的请求就好了。这里就不过多演示

仓库地址

有需要的朋友也可以直接去我的仓库看相关代码。如有错漏,还请指正。github.com/NopeDl/ez-rpc

到此这篇关于基于HTTP协议实现简单RPC框架的方法详解的文章就介绍到这了,更多相关RPC框架内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 关于feign.codec.DecodeException异常的解决方案

    关于feign.codec.DecodeException异常的解决方案

    这篇文章主要介绍了关于feign.codec.DecodeException异常的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • Springboot如何通过自定义工具类获取bean

    Springboot如何通过自定义工具类获取bean

    这篇文章主要介绍了Springboot通过自定义工具类获取bean方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • springboot通过jar包启动中文日志乱码问题及解决

    springboot通过jar包启动中文日志乱码问题及解决

    这篇文章主要介绍了springboot通过jar包启动中文日志乱码问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06
  • Java的SpringMVC中控制器返回XML数据问题

    Java的SpringMVC中控制器返回XML数据问题

    这篇文章主要介绍了Java的SpringMVC中控制器返回XML数据问题,控制器是处理HTTP请求的组件,它们接收来自客户端的请求,并将其转换为适当的响应,这些响应可以是动态生成的 HTML 页面,也可以是JSON或XML格式的数据,需要的朋友可以参考下
    2023-07-07
  • Java算法之重新排列数组例题

    Java算法之重新排列数组例题

    这篇文章主要介绍了Java算法之重新排列数组例题,文章围绕主题展开详细的内容介绍,具有一定的参考价值,感兴趣的小伙伴可以参考一下
    2022-08-08
  • Java中List遍历删除元素remove()的方法

    Java中List遍历删除元素remove()的方法

    这篇文章主要介绍了Java中List遍历删除元素remove()的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11
  • 深入分析JAVA Synchronized关键字

    深入分析JAVA Synchronized关键字

    这篇文章主要介绍了析JAVA Synchronized关键字的相关知识,文中代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-06-06
  • Java实现微信支付的签名算法示例

    Java实现微信支付的签名算法示例

    这篇文章主要为大家介绍了Java实现微信支付的签名算法实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • SpringBoot生产环境和测试环境配置分离的教程详解

    SpringBoot生产环境和测试环境配置分离的教程详解

    这篇文章主要介绍了SpringBoot生产环境和测试环境配置分离的教程详解,需要的朋友可以参考下
    2020-08-08
  • 关于log4j漏洞修复解决方案及源码编译

    关于log4j漏洞修复解决方案及源码编译

    Log4j 是Apache为Java提供的日志管理工具。他与System.out.println()的作用相似,用来跟踪、调试、维护程序。这篇文章主要介绍了关于log4j漏洞修复解决方案及源码编译,需要的朋友可以参考下
    2021-12-12

最新评论