根据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);
    }
}

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

  1. 获取@RPC注解中的途径
  2. 让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;
    }
}

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

  1. 把对应包下的接口全部扫描出来,包装成beanDefinition。
  2. 因为此刻的beanDefinition里边的类是一个接口,spring没办法帮咱们生成,所以咱们需求用一个beanFactory代替beanDefinition里的类,也就是用bean工厂帮咱们生成一个署理类给spring管理
  3. 添加实例化工厂需求的参数

工厂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-r…

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。