上篇讲了RPC服务端的实现。原理就是解析netty通道数据拿到类、方法及入参等信息,然后通过java反射机制调用本地接口返回结果。没有用到很复杂的技术。

这篇我们将客户端的实现。说白了客户端的任务很简单:一是建立socket长连接。二是封装发送服务端需要的数据包。三是处理返回结果。

demo地址

https://gitee.com/syher/grave-netty

RPC实现

同样定义注解扫描service接口。

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target({ElementType.TYPE})
  3. @Documented
  4. @Import({NettyClientScannerRegistrar.class, NettyClientApplicationContextAware.class})
  5. public @interface NettyClientScan {
  6.  
  7. String[] basePackages();
  8.  
  9. Class<? extends NettyFactoryBean> factoryBean() default NettyFactoryBean.class;
  10. }

  

该注解用于spring boot启动类上,参数basePackages指定接口所在的包路径。

  1. @SpringBootApplication
  2. @NettyClientScan(basePackages = {
  3. "com.braska.grave.netty.api.service"
  4. })
  5. public class GraveNettyClientApplication {
  6.  
  7. public static void main(String[] args) {
  8. SpringApplication.run(GraveNettyClientApplication.class, args);
  9. }
  10.  
  11. }

  

NettyServerScannerRegistrar类注册bean。

  1. public class NettyClientScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
  2.  
  3. @Override
  4. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  5. // spring bean注册
  6. NettyClientInterfaceScanner scanner = new NettyClientInterfaceScanner(registry);
  7.  
  8. AnnotationAttributes annoAttrs =
  9. AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(NettyClientScan.class.getName()));
  10.  
  11. Class<? extends NettyFactoryBean> nettyFactoryBeanClass = annoAttrs.getClass("factoryBean");
  12. if (!NettyFactoryBean.class.equals(nettyFactoryBeanClass)) {
  13. scanner.setNettyFactoryBean(BeanUtils.instantiateClass(nettyFactoryBeanClass));
  14. }
  15.  
  16. List<String> basePackages = new ArrayList<String>();
  17. for (String pkg : annoAttrs.getStringArray("basePackages")) {
  18. if (StringUtils.hasText(pkg)) {
  19. basePackages.add(pkg);
  20. }
  21. }
  22.  
  23. scanner.doScan(StringUtils.toStringArray(basePackages));
  24. }
  25. }

  

NettyClientInterfaceScanner类使用jdk动态代理basePackages路径下的接口。

  1. public class NettyClientInterfaceScanner extends ClassPathBeanDefinitionScanner {
  2. private NettyFactoryBean nettyFactoryBean = new NettyFactoryBean();
  3.  
  4. @Override
  5. public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  6. Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
  7.  
  8. if (beanDefinitions.isEmpty()) {
  9. } else {
  10. processBeanDefinitions(beanDefinitions);
  11. }
  12.  
  13. return beanDefinitions;
  14. }
  15.  
  16. private void processBeanDefinitions(
  17. Set<BeanDefinitionHolder> beanDefinitions) {
  18.  
  19. GenericBeanDefinition definition;
  20.  
  21. for (BeanDefinitionHolder holder : beanDefinitions) {
  22.  
  23. definition = (GenericBeanDefinition) holder.getBeanDefinition();
  24. // 为对象属性赋值(这一块我也还不太明白)
  25.        definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
  26. // 这里的nettyFactoryBean是生成Bean实例的工厂,不是Bean本身
  27. definition.setBeanClass(this.nettyFactoryBean.getClass());
  28.  
  29. definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
  30. }
  31. }
  32. }

  

NettyFactoryBean

  1. public class NettyFactoryBean<T> implements FactoryBean<T> {
  2. private Class<T> nettyInterface;
  3.  
  4. public NettyFactoryBean() {}
  5.  
  6. public NettyFactoryBean(Class<T> nettyInterface) {
  7. this.nettyInterface = nettyInterface;
  8. }
  9.  
  10. @Override
  11. public T getObject() throws Exception {
  12. // 通过jdk动态代理创建实例
  13. return (T) Proxy.newProxyInstance(nettyInterface.getClassLoader(), new Class[]{nettyInterface}, c.getInstance());
  14. }
  15.  
  16. @Override
  17. public Class<?> getObjectType() {
  18. return this.nettyInterface;
  19. }
  20.  
  21. @Override
  22. public boolean isSingleton() {
  23. return true;
  24. }
  25. }

  

关键来了,NettyInterfaceInvoker类负责数据包封装及发送。

  1. public class NettyInterfaceInvoker implements InvocationHandler {
  2.  
  3. private RequestSender sender;
  4. // 静态内部类做单例模式
  5. private static class SINGLETON {
  6. private static final NettyInterfaceInvoker invoker = new NettyInterfaceInvoker();
  7.  
  8. private static NettyInterfaceInvoker setSender(RequestSender sender) {
  9. invoker.sender = sender;
  10. return invoker;
  11. }
  12. }
  13.  
  14. public static NettyInterfaceInvoker getInstance() {
  15. return SINGLETON.invoker;
  16. }
  17.  
  18. public static NettyInterfaceInvoker setSender(RequestSender sender) {
  19. return SINGLETON.setSender(sender);
  20. }
  21.  
  22. @Override
  23. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  24. // 数据包封装,包含类名、方法名及参数等信息。
  25. Request request = new Request();
  26. request.setClassName(method.getDeclaringClass().getName());
  27. request.setMethodName(method.getName());
  28. request.setParameters(args);
  29. request.setParameterTypes(method.getParameterTypes());
  30. request.setId(UUID.randomUUID().toString());
  31. // 数据发送
  32. Object result = sender.send(request);
  33. Class<?> returnType = method.getReturnType();
  34. // 处理返回数据
  35. Response response = JSON.parseObject(result.toString(), Response.class);
  36. if (response.getCode() == 1) {
  37. throw new Exception(response.getError());
  38. }
  39. if (returnType.isPrimitive() || String.class.isAssignableFrom(returnType)) {
  40. return response.getData();
  41. } else if (Collection.class.isAssignableFrom(returnType)) {
  42. return JSONArray.parseArray(response.getData().toString(), Object.class);
  43. } else if (Map.class.isAssignableFrom(returnType)) {
  44. return JSON.parseObject(response.getData().toString(), Map.class);
  45. } else {
  46. Object data = response.getData();
  47. return JSONObject.parseObject(data.toString(), returnType);
  48. }
  49. }
  50. }

  

接着我们来看看RequestSender怎么处理数据的。

  1. public interface RequestSender {
  2. Channel connect(SocketAddress address) throws InterruptedException;
  3.  
  4. Object send(Request request) throws InterruptedException;
  5. }

  

RequestSender本身只是一个接口。他的实现类有:

  1. public class NettyClientApplicationContextAware extends ChannelInitializer<SocketChannel>
  2. implements RequestSender, ApplicationContextAware, InitializingBean {
  3. private static final Logger logger = Logger.getLogger(NettyClientApplicationContextAware.class.getName());
  4.  
  5. private String remoteAddress;
  6. private Bootstrap bootstrap;
  7. private EventLoopGroup group;
  8. private NettyChannelManager manager;
  9. private NettyClientHandler handler;
  10.  
  11. @Override
  12. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  13. this.remoteAddress = applicationContext.getEnvironment().getProperty("remoteAddress");
  14. this.bootstrap = new Bootstrap();
  15. this.group = new NioEventLoopGroup(1);
  16. this.bootstrap.group(group).
  17. channel(NioSocketChannel.class).
  18. option(ChannelOption.TCP_NODELAY, true).
  19. option(ChannelOption.SO_KEEPALIVE, true).
  20. handler(this);
  21. this.manager = new NettyChannelManager(this);
  22. this.handler = new NettyClientHandler(manager, remoteAddress);
  23. }
  24.  
  25. @Override
  26. public void afterPropertiesSet() throws Exception {
  27. // socket连接入口。
  28. this.manager.refresh(Lists.newArrayList(remoteAddress));
  29. }
  30.  
  31. @Override
  32. public Object send(Request request) throws InterruptedException {
  33. Channel channel = manager.take();
  34. if (channel != null && channel.isActive()) {
  35. SynchronousQueue<Object> queue = this.handler.sendRequest(request, channel);
  36. Object result = queue.take();
  37. return JSONArray.toJSONString(result);
  38. } else {
  39. Response res = new Response();
  40. res.setCode(1);
  41. res.setError("未正确连接到服务器.请检查相关配置信息!");
  42. return JSONArray.toJSONString(res);
  43. }
  44. }
  45.  
  46. @Override
  47. protected void initChannel(SocketChannel channel) throws Exception {
  48. ChannelPipeline pipeline = channel.pipeline();
  49. pipeline.addLast(new IdleStateHandler(0, 0, 30));
  50. pipeline.addLast(new JSONEncoder());
  51. pipeline.addLast(new JSONDecoder());
  52. // 管道处理器
  53. pipeline.addLast(this.handler);
  54. }
  55.  
  56. @Override
  57. public Channel connect(SocketAddress address) throws InterruptedException {
  58. ChannelFuture future = bootstrap.connect(address);
  59. // 建立长连接,提供失败重连。
  60. future.addListener(new ConnectionListener(this.manager, this.remoteAddress));
  61. Channel channel = future.channel();//future.sync().channel();
  62. return channel;
  63. }
  64.  
  65. public void destroy() {
  66. this.group.shutdownGracefully();
  67. }
  68. }

  

NettyClientHandler类处理管道事件。与服务端不通,这个管道处理器是继承ChannelInboundHandlerAdapter类。

  1. @ChannelHandler.Sharable
  2. public class NettyClientHandler extends ChannelInboundHandlerAdapter {
  3. private static final Logger logger = Logger.getLogger(NettyServerHandler.class.getName());
  4.  
  5. private ConcurrentHashMap<String, SynchronousQueue<Object>> queueMap = new ConcurrentHashMap<>();
  6. private NettyChannelManager manager;
  7. private String remoteAddress;
  8.  
  9. public NettyClientHandler(NettyChannelManager manager, String remoteAddress) {
  10. this.manager = manager;
  11. this.remoteAddress = remoteAddress;
  12. }
  13.  
  14. @Override
  15. public void channelInactive(ChannelHandlerContext ctx) {
  16. InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress();
  17. logger.info("与netty服务器断开连接." + address);
  18. ctx.channel().close();
  19. manager.remove(ctx.channel());
  20. // 掉线重连
  21. final EventLoop eventLoop = ctx.channel().eventLoop();
  22. eventLoop.schedule(() -> {
  23. manager.refresh(Lists.newArrayList(remoteAddress));
  24. }, 1L, TimeUnit.SECONDS);
  25. }
  26.  
  27. @Override
  28. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  29. // 处理服务端返回的数据
  30. Response response = JSON.parseObject(msg.toString(), Response.class);
  31. String requestId = response.getRequestId();
  32. SynchronousQueue<Object> queue = queueMap.get(requestId);
  33. queue.put(response);
  34. queueMap.remove(requestId);
  35. }
  36.  
  37. public SynchronousQueue<Object> sendRequest(Request request, Channel channel) {
  38. // 使用阻塞队列处理客户端请求
  39. SynchronousQueue<Object> queue = new SynchronousQueue<>();
  40. queueMap.put(request.getId(), queue);
  41. channel.writeAndFlush(request);
  42. return queue;
  43. }
  44.  
  45. public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
  46. logger.info("发送心跳消息...");
  47. if (evt instanceof IdleStateEvent) {
  48. IdleStateEvent event = (IdleStateEvent) evt;
  49. if (event.state() == IdleState.ALL_IDLE) {
  50. Request request = new Request();
  51. request.setMethodName("heartBeat");
  52. ctx.channel().writeAndFlush(request);
  53. }
  54. } else {
  55. super.userEventTriggered(ctx, evt);
  56. }
  57. }
  58. }

  

这样,RPC的客户端就写好了,其中主要涉及到的关键内容就是netty实例及管道处理器、jdk动态代理、还有一个阻塞队列。

结合上篇RPC服务端。一个完整的RPC框架就搭建完了。

当然,有些地方处理的还是比较粗糙。后续有修改以git代码为准。

基于netty实现rpc框架-spring boot客户端的更多相关文章

  1. 基于netty实现rpc框架-spring boot服务端

    demo地址 https://gitee.com/syher/grave-netty RPC介绍 首先了解一下RPC:远程过程调用.简单点说就是本地应用可以调用远程服务器的接口.那么通过什么方式调用远 ...

  2. 这样基于Netty重构RPC框架你不可能知道

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365天原创计划”第5天. 今天呢!灯塔君跟大家讲: 基于Netty重构RPC框架 一.CyclicBarrier方法说明 1. ...

  3. 《Java 编写基于 Netty 的 RPC 框架》

    一 简单概念 RPC: ( Remote Procedure Call),远程调用过程,是通过网络调用远程计算机的进程中某个方法,从而获取到想要的数据,过程如同调用本地的方法一样. 阻塞IO :当阻塞 ...

  4. 基于Netty重构RPC框架

    下面的这张图,大概很多小伙伴都见到过,这是Dubbo 官网中的一张图描述了项目架构的演进过程.随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在 ...

  5. java编写基于netty的RPC框架

    一 简单概念 RPC:(Remote Procedure Call),远程调用过程,是通过网络调用远程计算机的进程中某个方法,从而获取到想要的数据,过程如同调用本地的方法一样. 阻塞IO:当阻塞I/O ...

  6. DIY一些基于netty的开源框架

    几款基于netty的开源框架,有益于对netty的理解和学习! 基于netty的http server框架 https://github.com/TogetherOS/cicada 基于netty的即 ...

  7. 基于Netty打造RPC服务器设计经验谈

    自从在园子里,发表了两篇如何基于Netty构建RPC服务器的文章:谈谈如何使用Netty开发实现高性能的RPC服务器.Netty实现高性能RPC服务器优化篇之消息序列化 之后,收到了很多同行.园友们热 ...

  8. 基于Netty的RPC简易实现

    代码地址如下:http://www.demodashi.com/demo/13448.html 可以给你提供思路 也可以让你学到Netty相关的知识 当然,这只是一种实现方式 需求 看下图,其实这个项 ...

  9. 实现基于netty的web框架,了解一下

    上一篇写了,基于netty实现的rpc的微框架,其中详细介绍netty的原理及组件,这篇就不过多介绍 这篇实现基于netty的web框架,你说netty强不强,文中有不对的地方,欢迎大牛指正 先普及几 ...

随机推荐

  1. command > /dev/null command > /dev/null 2>&1nohup command &> /dev/null的区别

    1.对以下命令进行依次区分 command 执行一条普通的命令 command > /dev/null   '>'表示将标准输出重定向 '>>'表示追加,/dev/null是一 ...

  2. Servlet读取前端的request payload

    这几天遇见了一个很头疼的事,当我想用表单上传文件时,后端servlet读取不到文件的信息 网上搜索,说是要将form添加这个属性enctype="multipart/form-data&qu ...

  3. 分享个Class工具类

    import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.net.JarURLCo ...

  4. iOS 静态:动态 Pod

    一.静态和动态 在项目中使用 pod 实现模块化,对于子模块和第三类库的导入方式存在两种:静态库.动态库. 当在 podfile 中指定 use_frameworks! 时,子模块和第三方类库将被打包 ...

  5. 《java编程思想》操作符

    1. 自动递增和递减 递增和递减运算是两种相当不错的快捷运算,递减操作符是 "--",意为减少一个单位,递增操作符是 "++",意为增加一个单位.这两个操作符各 ...

  6. python数据分析工具 | matplotlib

    不论是数据挖掘还是数学建模,都免不了数据可视化的问题.对于 Python 来说,matplotlib 是最著名的绘图库,它主要用于二维绘图,当然也可以进行简单的三维绘图.它不但提供了一整套和 Matl ...

  7. Java中的get()方法和set()方法

    在Java中,为了数据的安全,换句话说就是为了隐藏你的代码的一些实现细节,我们会用private来修饰属性,使用private修饰的属性就不能被其他类直接访问了,想要访问就需要通过set.get方法: ...

  8. Vue引用阿里图标库

    首先进入官网http://www.iconfont.cn/ 转载:https://blog.csdn.net/qq_34802010/article/details/81451278 选择图标库 在里 ...

  9. iSCSI集群与存储

                                                                                                        ...

  10. 配置一个mariadb数据库《二》

                                                             mariadb 配置一个数据库 案例4:配置一个数据库 4.1 问题 本例要求在虚拟机 ...