记录EasyRpc开发过程中遇到的问题及解决思路,包含netty,springboot-starter,spring动态注入FactoryBean生成代理实现类,异步阻塞返回请求结果等
0x00 项目地址
EasyRpc:A simple rpc framework implemented with netty for learning. And provide easyrpc-spring-boot-starter
0x01 easyrpc-core
这一块基础部分比较简单主要是netty通信、包装request请求和response,加上json的编码解码器。
问题:服务端接收到请求后如何找到对应的类反射调用?
微服务系统中一般interface接口是单独一个jar包,需求是如何扫描到classpath下的文件或者jar下的文件;1
2
3
4//如果只是通过
//获取到classpath下包括jar中的对应包目录下的文件列表
Enumeration<URL> dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
//遍历带有@RpcService注解的类,放入map中(避免每次请求request都扫描并且反射消耗性能)待后续调用;
难点2:如何异步返回请求结果给代理类?;
等0x02看完之后,再引申出这个问题
0x02 spring-boot-starter
开启@EnableRpc或@EnableRpcServer后自动开启配置类或者动态注入bean使用@import注解
server-starter
简单直接调用rpcServer.start()启动;
client-starter
难点:客户端只有一个interface,如何通过@Autowired自动注入使用?
首先需要使用肯定是需要实例类的,只能通过动态代理为接口生成一个实例类,类中包装request并请求返回数据;
然后如何交给spring容器管理呢。这里就需要用到动态注入BeanDefinition技术;
两种方式:
- implements BeanDefinitionRegistryPostProcessor
- implements ImportBeanDefinitionRegistrar
基本原理是通过后置处理器等待bean容器准备好之后,动态注入BeanDefinition,然后spring继续执行bean初始化的操作
这时候需要用到FactoryBean,自定义ProxyFactory实现FactoryBean,getObject()方法中指定动态代理返回代理类。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
//扫描到带有固定注解并且需要被动态注入的class
//todo 这里的报名可以使用@ConfigurationProperties来放到配置文件中
List<Class<?>> annotationClassList = AnnoManageUtil.getPackageController("com.welljay.easyrpc.test.jar", RpcService.class);
//动态注入beanDefinition
annotationClassList.forEach(c -> {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(c);
GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();
//这儿非常重要!!!否则@Autowired的时候找不到对应的type注入
//@Autowired默认byType,其次byName
definition.getPropertyValues().add("interfaceClass", definition.getBeanClassName());
definition.setBeanClass(ProxyFactory.class);
definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
String simpleName = classToLowerName(c);
registry.registerBeanDefinition(simpleName, definition);
});
}
因为这儿是代理类需要返回值所以引申出来上面的难点
难点:netty默认服务端writeAndFlush都是直接进入客户端的handler的void read()方法,如何返回值给调用的代理类?
netty为我们提供了一个类DefaultPromise<V> extends AbstractFuture<V> implements Promise<V>可以用来获取结果;
它包含方法Promise<V> setSuccess(V result)标识该Promise已经成功获取到结果,他的父抽象类包含方法get()阻塞获取值;
所以我们可以自己继承一个RpcFuture extends DefaultPromise<RpcResponse>,并创建一个FutureHolder类,其中来保存一个FastThreadLocal(Netty提供的ThreadLocal)包含HashMap<请求ID, RpcFuture>;
当代理类调用客户端发送请求的时候会注册一个CompleteListener到ChannelFuture,等channel请求完成后,将会把当前的请求Id和new RpcFuture(channelFuture.channel().eventLoop())注册到FutureHolder中,并且下面rpcFuture.get()阻塞获取返回值,等待客户端收到响应;
当我们的客户端handler收到read()方法响应的时候,把结果传入并且把RpcFuture标记成已经获取到结果;1
2
3
4
5
6
7
protected void channelRead0(ChannelHandlerContext channelHandlerContext, RpcResponse rpcResponse) throws Exception {
RpcFuture rpcFuture = FutureHolder.getAndRemoveFuture(rpcResponse.getRequestId());
if (rpcFuture != null) {
rpcFuture.setSuccess(rpcResponse);
}
}
rpcFuture.get()就会获取到返回值,直接return给代理类;