基于netty实现rpc框架-spring boot客户端
上篇讲了RPC服务端的实现。原理就是解析netty通道数据拿到类、方法及入参等信息,然后通过java反射机制调用本地接口返回结果。没有用到很复杂的技术。
这篇我们将客户端的实现。说白了客户端的任务很简单:一是建立socket长连接。二是封装发送服务端需要的数据包。三是处理返回结果。
demo地址
https://gitee.com/syher/grave-netty
RPC实现
同样定义注解扫描service接口。
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.TYPE})
- @Documented
- @Import({NettyClientScannerRegistrar.class, NettyClientApplicationContextAware.class})
- public @interface NettyClientScan {
- String[] basePackages();
- Class<? extends NettyFactoryBean> factoryBean() default NettyFactoryBean.class;
- }
该注解用于spring boot启动类上,参数basePackages指定接口所在的包路径。
- @SpringBootApplication
- @NettyClientScan(basePackages = {
- "com.braska.grave.netty.api.service"
- })
- public class GraveNettyClientApplication {
- public static void main(String[] args) {
- SpringApplication.run(GraveNettyClientApplication.class, args);
- }
- }
NettyServerScannerRegistrar类注册bean。
- public class NettyClientScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
- @Override
- public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
- // spring bean注册
- NettyClientInterfaceScanner scanner = new NettyClientInterfaceScanner(registry);
- AnnotationAttributes annoAttrs =
- AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(NettyClientScan.class.getName()));
- Class<? extends NettyFactoryBean> nettyFactoryBeanClass = annoAttrs.getClass("factoryBean");
- if (!NettyFactoryBean.class.equals(nettyFactoryBeanClass)) {
- scanner.setNettyFactoryBean(BeanUtils.instantiateClass(nettyFactoryBeanClass));
- }
- List<String> basePackages = new ArrayList<String>();
- for (String pkg : annoAttrs.getStringArray("basePackages")) {
- if (StringUtils.hasText(pkg)) {
- basePackages.add(pkg);
- }
- }
- scanner.doScan(StringUtils.toStringArray(basePackages));
- }
- }
NettyClientInterfaceScanner类使用jdk动态代理basePackages路径下的接口。
- public class NettyClientInterfaceScanner extends ClassPathBeanDefinitionScanner {
- private NettyFactoryBean nettyFactoryBean = new NettyFactoryBean();
- @Override
- public Set<BeanDefinitionHolder> doScan(String... basePackages) {
- Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
- if (beanDefinitions.isEmpty()) {
- } else {
- processBeanDefinitions(beanDefinitions);
- }
- return beanDefinitions;
- }
- private void processBeanDefinitions(
- Set<BeanDefinitionHolder> beanDefinitions) {
- GenericBeanDefinition definition;
- for (BeanDefinitionHolder holder : beanDefinitions) {
- definition = (GenericBeanDefinition) holder.getBeanDefinition();
- // 为对象属性赋值(这一块我也还不太明白)
- definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
- // 这里的nettyFactoryBean是生成Bean实例的工厂,不是Bean本身
- definition.setBeanClass(this.nettyFactoryBean.getClass());
- definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
- }
- }
- }
NettyFactoryBean
- public class NettyFactoryBean<T> implements FactoryBean<T> {
- private Class<T> nettyInterface;
- public NettyFactoryBean() {}
- public NettyFactoryBean(Class<T> nettyInterface) {
- this.nettyInterface = nettyInterface;
- }
- @Override
- public T getObject() throws Exception {
- // 通过jdk动态代理创建实例
- return (T) Proxy.newProxyInstance(nettyInterface.getClassLoader(), new Class[]{nettyInterface}, c.getInstance());
- }
- @Override
- public Class<?> getObjectType() {
- return this.nettyInterface;
- }
- @Override
- public boolean isSingleton() {
- return true;
- }
- }
关键来了,NettyInterfaceInvoker类负责数据包封装及发送。
- public class NettyInterfaceInvoker implements InvocationHandler {
- private RequestSender sender;
- // 静态内部类做单例模式
- private static class SINGLETON {
- private static final NettyInterfaceInvoker invoker = new NettyInterfaceInvoker();
- private static NettyInterfaceInvoker setSender(RequestSender sender) {
- invoker.sender = sender;
- return invoker;
- }
- }
- public static NettyInterfaceInvoker getInstance() {
- return SINGLETON.invoker;
- }
- public static NettyInterfaceInvoker setSender(RequestSender sender) {
- return SINGLETON.setSender(sender);
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- // 数据包封装,包含类名、方法名及参数等信息。
- Request request = new Request();
- request.setClassName(method.getDeclaringClass().getName());
- request.setMethodName(method.getName());
- request.setParameters(args);
- request.setParameterTypes(method.getParameterTypes());
- request.setId(UUID.randomUUID().toString());
- // 数据发送
- Object result = sender.send(request);
- Class<?> returnType = method.getReturnType();
- // 处理返回数据
- Response response = JSON.parseObject(result.toString(), Response.class);
- if (response.getCode() == 1) {
- throw new Exception(response.getError());
- }
- if (returnType.isPrimitive() || String.class.isAssignableFrom(returnType)) {
- return response.getData();
- } else if (Collection.class.isAssignableFrom(returnType)) {
- return JSONArray.parseArray(response.getData().toString(), Object.class);
- } else if (Map.class.isAssignableFrom(returnType)) {
- return JSON.parseObject(response.getData().toString(), Map.class);
- } else {
- Object data = response.getData();
- return JSONObject.parseObject(data.toString(), returnType);
- }
- }
- }
接着我们来看看RequestSender怎么处理数据的。
- public interface RequestSender {
- Channel connect(SocketAddress address) throws InterruptedException;
- Object send(Request request) throws InterruptedException;
- }
RequestSender本身只是一个接口。他的实现类有:
- public class NettyClientApplicationContextAware extends ChannelInitializer<SocketChannel>
- implements RequestSender, ApplicationContextAware, InitializingBean {
- private static final Logger logger = Logger.getLogger(NettyClientApplicationContextAware.class.getName());
- private String remoteAddress;
- private Bootstrap bootstrap;
- private EventLoopGroup group;
- private NettyChannelManager manager;
- private NettyClientHandler handler;
- @Override
- public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
- this.remoteAddress = applicationContext.getEnvironment().getProperty("remoteAddress");
- this.bootstrap = new Bootstrap();
- this.group = new NioEventLoopGroup(1);
- this.bootstrap.group(group).
- channel(NioSocketChannel.class).
- option(ChannelOption.TCP_NODELAY, true).
- option(ChannelOption.SO_KEEPALIVE, true).
- handler(this);
- this.manager = new NettyChannelManager(this);
- this.handler = new NettyClientHandler(manager, remoteAddress);
- }
- @Override
- public void afterPropertiesSet() throws Exception {
- // socket连接入口。
- this.manager.refresh(Lists.newArrayList(remoteAddress));
- }
- @Override
- public Object send(Request request) throws InterruptedException {
- Channel channel = manager.take();
- if (channel != null && channel.isActive()) {
- SynchronousQueue<Object> queue = this.handler.sendRequest(request, channel);
- Object result = queue.take();
- return JSONArray.toJSONString(result);
- } else {
- Response res = new Response();
- res.setCode(1);
- res.setError("未正确连接到服务器.请检查相关配置信息!");
- return JSONArray.toJSONString(res);
- }
- }
- @Override
- protected void initChannel(SocketChannel channel) throws Exception {
- ChannelPipeline pipeline = channel.pipeline();
- pipeline.addLast(new IdleStateHandler(0, 0, 30));
- pipeline.addLast(new JSONEncoder());
- pipeline.addLast(new JSONDecoder());
- // 管道处理器
- pipeline.addLast(this.handler);
- }
- @Override
- public Channel connect(SocketAddress address) throws InterruptedException {
- ChannelFuture future = bootstrap.connect(address);
- // 建立长连接,提供失败重连。
- future.addListener(new ConnectionListener(this.manager, this.remoteAddress));
- Channel channel = future.channel();//future.sync().channel();
- return channel;
- }
- public void destroy() {
- this.group.shutdownGracefully();
- }
- }
NettyClientHandler类处理管道事件。与服务端不通,这个管道处理器是继承ChannelInboundHandlerAdapter类。
- @ChannelHandler.Sharable
- public class NettyClientHandler extends ChannelInboundHandlerAdapter {
- private static final Logger logger = Logger.getLogger(NettyServerHandler.class.getName());
- private ConcurrentHashMap<String, SynchronousQueue<Object>> queueMap = new ConcurrentHashMap<>();
- private NettyChannelManager manager;
- private String remoteAddress;
- public NettyClientHandler(NettyChannelManager manager, String remoteAddress) {
- this.manager = manager;
- this.remoteAddress = remoteAddress;
- }
- @Override
- public void channelInactive(ChannelHandlerContext ctx) {
- InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress();
- logger.info("与netty服务器断开连接." + address);
- ctx.channel().close();
- manager.remove(ctx.channel());
- // 掉线重连
- final EventLoop eventLoop = ctx.channel().eventLoop();
- eventLoop.schedule(() -> {
- manager.refresh(Lists.newArrayList(remoteAddress));
- }, 1L, TimeUnit.SECONDS);
- }
- @Override
- public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
- // 处理服务端返回的数据
- Response response = JSON.parseObject(msg.toString(), Response.class);
- String requestId = response.getRequestId();
- SynchronousQueue<Object> queue = queueMap.get(requestId);
- queue.put(response);
- queueMap.remove(requestId);
- }
- public SynchronousQueue<Object> sendRequest(Request request, Channel channel) {
- // 使用阻塞队列处理客户端请求
- SynchronousQueue<Object> queue = new SynchronousQueue<>();
- queueMap.put(request.getId(), queue);
- channel.writeAndFlush(request);
- return queue;
- }
- public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
- logger.info("发送心跳消息...");
- if (evt instanceof IdleStateEvent) {
- IdleStateEvent event = (IdleStateEvent) evt;
- if (event.state() == IdleState.ALL_IDLE) {
- Request request = new Request();
- request.setMethodName("heartBeat");
- ctx.channel().writeAndFlush(request);
- }
- } else {
- super.userEventTriggered(ctx, evt);
- }
- }
- }
这样,RPC的客户端就写好了,其中主要涉及到的关键内容就是netty实例及管道处理器、jdk动态代理、还有一个阻塞队列。
结合上篇RPC服务端。一个完整的RPC框架就搭建完了。
当然,有些地方处理的还是比较粗糙。后续有修改以git代码为准。
基于netty实现rpc框架-spring boot客户端的更多相关文章
- 基于netty实现rpc框架-spring boot服务端
demo地址 https://gitee.com/syher/grave-netty RPC介绍 首先了解一下RPC:远程过程调用.简单点说就是本地应用可以调用远程服务器的接口.那么通过什么方式调用远 ...
- 这样基于Netty重构RPC框架你不可能知道
原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365天原创计划”第5天. 今天呢!灯塔君跟大家讲: 基于Netty重构RPC框架 一.CyclicBarrier方法说明 1. ...
- 《Java 编写基于 Netty 的 RPC 框架》
一 简单概念 RPC: ( Remote Procedure Call),远程调用过程,是通过网络调用远程计算机的进程中某个方法,从而获取到想要的数据,过程如同调用本地的方法一样. 阻塞IO :当阻塞 ...
- 基于Netty重构RPC框架
下面的这张图,大概很多小伙伴都见到过,这是Dubbo 官网中的一张图描述了项目架构的演进过程.随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在 ...
- java编写基于netty的RPC框架
一 简单概念 RPC:(Remote Procedure Call),远程调用过程,是通过网络调用远程计算机的进程中某个方法,从而获取到想要的数据,过程如同调用本地的方法一样. 阻塞IO:当阻塞I/O ...
- DIY一些基于netty的开源框架
几款基于netty的开源框架,有益于对netty的理解和学习! 基于netty的http server框架 https://github.com/TogetherOS/cicada 基于netty的即 ...
- 基于Netty打造RPC服务器设计经验谈
自从在园子里,发表了两篇如何基于Netty构建RPC服务器的文章:谈谈如何使用Netty开发实现高性能的RPC服务器.Netty实现高性能RPC服务器优化篇之消息序列化 之后,收到了很多同行.园友们热 ...
- 基于Netty的RPC简易实现
代码地址如下:http://www.demodashi.com/demo/13448.html 可以给你提供思路 也可以让你学到Netty相关的知识 当然,这只是一种实现方式 需求 看下图,其实这个项 ...
- 实现基于netty的web框架,了解一下
上一篇写了,基于netty实现的rpc的微框架,其中详细介绍netty的原理及组件,这篇就不过多介绍 这篇实现基于netty的web框架,你说netty强不强,文中有不对的地方,欢迎大牛指正 先普及几 ...
随机推荐
- command > /dev/null command > /dev/null 2>&1nohup command &> /dev/null的区别
1.对以下命令进行依次区分 command 执行一条普通的命令 command > /dev/null '>'表示将标准输出重定向 '>>'表示追加,/dev/null是一 ...
- Servlet读取前端的request payload
这几天遇见了一个很头疼的事,当我想用表单上传文件时,后端servlet读取不到文件的信息 网上搜索,说是要将form添加这个属性enctype="multipart/form-data&qu ...
- 分享个Class工具类
import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.net.JarURLCo ...
- iOS 静态:动态 Pod
一.静态和动态 在项目中使用 pod 实现模块化,对于子模块和第三类库的导入方式存在两种:静态库.动态库. 当在 podfile 中指定 use_frameworks! 时,子模块和第三方类库将被打包 ...
- 《java编程思想》操作符
1. 自动递增和递减 递增和递减运算是两种相当不错的快捷运算,递减操作符是 "--",意为减少一个单位,递增操作符是 "++",意为增加一个单位.这两个操作符各 ...
- python数据分析工具 | matplotlib
不论是数据挖掘还是数学建模,都免不了数据可视化的问题.对于 Python 来说,matplotlib 是最著名的绘图库,它主要用于二维绘图,当然也可以进行简单的三维绘图.它不但提供了一整套和 Matl ...
- Java中的get()方法和set()方法
在Java中,为了数据的安全,换句话说就是为了隐藏你的代码的一些实现细节,我们会用private来修饰属性,使用private修饰的属性就不能被其他类直接访问了,想要访问就需要通过set.get方法: ...
- Vue引用阿里图标库
首先进入官网http://www.iconfont.cn/ 转载:https://blog.csdn.net/qq_34802010/article/details/81451278 选择图标库 在里 ...
- iSCSI集群与存储
...
- 配置一个mariadb数据库《二》
mariadb 配置一个数据库 案例4:配置一个数据库 4.1 问题 本例要求在虚拟机 ...